Comparar commits
84 Commits
| Autor | SHA1 | Data | |
|---|---|---|---|
| 78dd352e71 | |||
| 118b480690 | |||
| e3e9974888 | |||
| 0c237e0116 | |||
| bd534b3216 | |||
| 7393c0603b | |||
| 17ef476a32 | |||
| ef5857e3a2 | |||
| 92f27fb400 | |||
| 1e4401c68b | |||
| 64460c3fe6 | |||
| 4de6d83bf4 | |||
| efe6fe2829 | |||
| 7810048145 | |||
| 77d62d797f | |||
| a0fc9925d9 | |||
| b1fa763845 | |||
| 0329ea5703 | |||
| 1640a96e95 | |||
| 60c0ab004c | |||
| 5e4ea5b750 | |||
| 3fece01fd4 | |||
| 39de35ea28 | |||
| 6f9a5a260c | |||
| 29777e0770 | |||
| 3ff08885f3 | |||
| f76ac21401 | |||
| 5e41f2328b | |||
| 8041e70ac3 | |||
| 1c9682351c | |||
| f41b60515f | |||
| 544dec0433 | |||
| 29382c8369 | |||
| 29903ceabf | |||
| 03312d6f14 | |||
| 474965a679 | |||
| abc3631d06 | |||
| ba9b5cbd04 | |||
| f53868166b | |||
| bcd225a5fb | |||
| 33e9dad276 | |||
| f9b1e50ebe | |||
| dc3deb6bf9 | |||
| 580e3fc93e | |||
| c6d5c0e210 | |||
| 8cef10d64a | |||
| 28114d108a | |||
| b63405d3b3 | |||
| d6fac735cb | |||
| 1ee9de08c8 | |||
| 6d0859579a | |||
| 782af8c06a | |||
| be7b68acb1 | |||
| ead11736ca | |||
| 4c851dd637 | |||
| 0b6bec2ef0 | |||
| eb6a072964 | |||
| 3c79f2e3b0 | |||
| a09fa51221 | |||
| 1da42774a4 | |||
| 93ae6b5627 | |||
| 46c45645df | |||
| 1725f2d0b8 | |||
| 89705f9e64 | |||
| 9294f853c3 | |||
| 56f9d91560 | |||
| b7e694f62b | |||
| 8cfffd2577 | |||
| 75c262f1f3 | |||
| 901b472ea1 | |||
| 84c199b0db | |||
| 1571d22e9b | |||
| 538bb08632 | |||
| a604e4943c | |||
| f61c0c2637 | |||
| 1e9ddf63e5 | |||
| d0536238b5 | |||
| 43fb52bb88 | |||
| b0102288cd | |||
| 7e1a2ecd5e | |||
| c7250e5932 | |||
| 1ff37171e7 | |||
| 5a102f5f28 | |||
| 96ca12aaf0 |
@@ -0,0 +1,3 @@
|
||||
Language: JavaScript
|
||||
BasedOnStyle: Google
|
||||
ColumnLimit: 100
|
||||
@@ -0,0 +1,14 @@
|
||||
# http://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
max_line_length = 0
|
||||
trim_trailing_whitespace = false
|
||||
+28
-2
@@ -1,2 +1,28 @@
|
||||
.idea
|
||||
node_modules
|
||||
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# compiled output
|
||||
/dist
|
||||
/tmp
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/bower_components
|
||||
|
||||
# IDEs and editors
|
||||
/.idea
|
||||
|
||||
# misc
|
||||
/.sass-cache
|
||||
/connect.lock
|
||||
/coverage/*
|
||||
/libpeerconnection.log
|
||||
npm-debug.log
|
||||
testem.log
|
||||
/typings
|
||||
|
||||
# e2e
|
||||
/e2e/*.js
|
||||
/e2e/*.map
|
||||
|
||||
#System Files
|
||||
.DS_Store
|
||||
+6
-4
@@ -1,4 +1,6 @@
|
||||
# OpenBCI Visualizer
|
||||
# OpenBCI Dashboard
|
||||
|
||||

|
||||
|
||||
A fullstack javascript app for capturing and visualizing OpenBCI EEG data
|
||||
|
||||
@@ -11,15 +13,15 @@ This project is under development, this is just a first draft.
|
||||
|
||||
```bash
|
||||
npm install
|
||||
node visualizer
|
||||
npm run visualize
|
||||
```
|
||||
|
||||
* Go to: http://localhost:3036
|
||||
* Go to: http://localhost:4200
|
||||
|
||||
## Simulating data
|
||||
|
||||
```bash
|
||||
node visualizer simulate
|
||||
npm run simulate
|
||||
```
|
||||
|
||||
## Support
|
||||
|
||||
externo
+24
@@ -0,0 +1,24 @@
|
||||
/* global require, module */
|
||||
|
||||
var Angular2App = require('angular-cli/lib/broccoli/angular2-app');
|
||||
|
||||
module.exports = function(defaults) {
|
||||
return new Angular2App(defaults, {
|
||||
vendorNpmFiles: [
|
||||
'socket.io-client/socket.io.js',
|
||||
'smoothie/smoothie.js',
|
||||
'chart.js/Chart.js',
|
||||
'ng2-charts/bundles/ng2-charts.js',
|
||||
'chroma-js/chroma.js',
|
||||
'plotly.js/dist/plotly.js',
|
||||
'phaser/dist/phaser.js',
|
||||
'systemjs/dist/system-polyfills.js',
|
||||
'systemjs/dist/system.src.js',
|
||||
'zone.js/dist/*.js',
|
||||
'es6-shim/es6-shim.js',
|
||||
'reflect-metadata/*.js',
|
||||
'rxjs/**/*.js',
|
||||
'@angular/**/*.js'
|
||||
]
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"project": {
|
||||
"version": "0.1.0",
|
||||
"name": "clitest"
|
||||
},
|
||||
"apps": [
|
||||
{"main": "src/main.ts", "tsconfig": "src/tsconfig.json"}
|
||||
],
|
||||
"addons": [],
|
||||
"packages": [],
|
||||
"e2e": {
|
||||
"protractor": {
|
||||
"config": "config/protractor.conf.js"
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"karma": {
|
||||
"config": "config/karma.conf.js"
|
||||
}
|
||||
},
|
||||
"defaults": {
|
||||
"prefix": "bci",
|
||||
"sourceDir": "src"
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
<section>
|
||||
<h2>Alpha</h2>
|
||||
<canvas id="alpha"
|
||||
class="chart chart-bar"
|
||||
chart-data="$ctrl.data"
|
||||
chart-labels="$ctrl.series"
|
||||
chart-series="$ctrl.series">
|
||||
</canvas>
|
||||
</section>
|
||||
@@ -1,17 +0,0 @@
|
||||
|
||||
angular.module('openbciVisualizer')
|
||||
.component('bciAlpha', {
|
||||
templateUrl: 'components/alpha/alpha.html',
|
||||
controller: function ($timeout) {
|
||||
var $ctrl = this;
|
||||
var socket = io();
|
||||
$ctrl.series = ['Channel 1','Channel 2','Channel 3','Channel 4','Channel 5','Channel 6','Channel 7','Channel 8'];
|
||||
socket.on('openBCIData', function (data) {
|
||||
var alphaRange = EEGSpectrumUtils.filterBand(data.spectrums.data, data.spectrums.labels, [8, 12]);
|
||||
$timeout(function () {
|
||||
$ctrl.labels = alphaRange.labels;
|
||||
$ctrl.data = alphaRange.spectrums;
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1,9 +0,0 @@
|
||||
<section>
|
||||
<h2>Beta</h2>
|
||||
<canvas id="beta"
|
||||
class="chart chart-bar"
|
||||
chart-data="$ctrl.data"
|
||||
chart-labels="$ctrl.series"
|
||||
chart-series="$ctrl.series">
|
||||
</canvas>
|
||||
</section>
|
||||
@@ -1,17 +0,0 @@
|
||||
|
||||
angular.module('openbciVisualizer')
|
||||
.component('bciBeta', {
|
||||
templateUrl: 'components/beta/beta.html',
|
||||
controller: function ($timeout) {
|
||||
var $ctrl = this;
|
||||
var socket = io();
|
||||
$ctrl.series = ['Channel 1','Channel 2','Channel 3','Channel 4','Channel 5','Channel 6','Channel 7','Channel 8'];
|
||||
socket.on('openBCIData', function (data) {
|
||||
var betaRange = EEGSpectrumUtils.filterBand(data.spectrums.data, data.spectrums.labels, [12, 40]);
|
||||
$timeout(function () {
|
||||
$ctrl.labels = betaRange.labels;
|
||||
$ctrl.data = betaRange.spectrums;
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1,9 +0,0 @@
|
||||
<section>
|
||||
<h2>Delta</h2>
|
||||
<canvas id="delta"
|
||||
class="chart chart-bar"
|
||||
chart-data="$ctrl.data"
|
||||
chart-labels="$ctrl.series"
|
||||
chart-series="$ctrl.series">
|
||||
</canvas>
|
||||
</section>
|
||||
@@ -1,17 +0,0 @@
|
||||
|
||||
angular.module('openbciVisualizer')
|
||||
.component('bciDelta', {
|
||||
templateUrl: 'components/delta/delta.html',
|
||||
controller: function ($timeout) {
|
||||
var $ctrl = this;
|
||||
var socket = io();
|
||||
$ctrl.series = ['Channel 1','Channel 2','Channel 3','Channel 4','Channel 5','Channel 6','Channel 7','Channel 8'];
|
||||
socket.on('openBCIData', function (data) {
|
||||
var deltaRange = EEGSpectrumUtils.filterBand(data.spectrums.data, data.spectrums.labels, [0.5, 4]);
|
||||
$timeout(function () {
|
||||
$ctrl.labels = deltaRange.labels;
|
||||
$ctrl.data = deltaRange.spectrums;
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1,10 +0,0 @@
|
||||
<section>
|
||||
<h2>Frequency</h2>
|
||||
<canvas id="frequency"
|
||||
class="chart chart-line"
|
||||
chart-data="$ctrl.data"
|
||||
chart-labels="$ctrl.labels"
|
||||
chart-series="$ctrl.series"
|
||||
chart-options="{ responsive: true }">
|
||||
</canvas>
|
||||
</section>
|
||||
@@ -1,16 +0,0 @@
|
||||
|
||||
angular.module('openbciVisualizer')
|
||||
.component('bciFrequency', {
|
||||
templateUrl: 'components/frequency/frequency.html',
|
||||
controller: function ($timeout) {
|
||||
var $ctrl = this;
|
||||
var socket = io();
|
||||
$ctrl.series = ['Channel 1','Channel 2','Channel 3','Channel 4','Channel 5','Channel 6','Channel 7','Channel 8'];
|
||||
socket.on('openBCIData', function (data) {
|
||||
$timeout(function () {
|
||||
$ctrl.data = data.spectrums.data;
|
||||
$ctrl.labels = data.spectrums.labels;
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1,9 +0,0 @@
|
||||
<section>
|
||||
<h2>Theta</h2>
|
||||
<canvas id="theta"
|
||||
class="chart chart-bar"
|
||||
chart-data="$ctrl.data"
|
||||
chart-labels="$ctrl.series"
|
||||
chart-series="$ctrl.series">
|
||||
</canvas>
|
||||
</section>
|
||||
@@ -1,17 +0,0 @@
|
||||
|
||||
angular.module('openbciVisualizer')
|
||||
.component('bciTheta', {
|
||||
templateUrl: 'components/theta/theta.html',
|
||||
controller: function ($timeout) {
|
||||
var $ctrl = this;
|
||||
var socket = io();
|
||||
$ctrl.series = ['Channel 1','Channel 2','Channel 3','Channel 4','Channel 5','Channel 6','Channel 7','Channel 8'];
|
||||
socket.on('openBCIData', function (data) {
|
||||
var thetaRange = EEGSpectrumUtils.filterBand(data.spectrums.data, data.spectrums.labels, [4, 8]);
|
||||
$timeout(function () {
|
||||
$ctrl.labels = thetaRange.labels;
|
||||
$ctrl.data = thetaRange.spectrums;
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1,10 +0,0 @@
|
||||
<section>
|
||||
<h2>Time</h2>
|
||||
<canvas id="time"
|
||||
class="chart chart-line"
|
||||
chart-data="$ctrl.data"
|
||||
chart-labels="$ctrl.labels"
|
||||
chart-series="$ctrl.series"
|
||||
chart-options="{ responsive: true, scaleOverride: false }">
|
||||
</canvas>
|
||||
</section>
|
||||
@@ -1,17 +0,0 @@
|
||||
|
||||
angular.module('openbciVisualizer')
|
||||
.component('bciTime', {
|
||||
templateUrl: 'components/time/time.html',
|
||||
controller: function ($timeout) {
|
||||
var $ctrl = this;
|
||||
var socket = io();
|
||||
$ctrl.series = ['Channel 1','Channel 2','Channel 3','Channel 4','Channel 5','Channel 6','Channel 7','Channel 8'];
|
||||
socket.on('openBCIData', function (data) {
|
||||
$timeout(function () {
|
||||
console.log(data.timeSeries);
|
||||
$ctrl.labels = data.timeSeries.labels;
|
||||
$ctrl.data = data.timeSeries.data;
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1,55 +0,0 @@
|
||||
<!doctype html>
|
||||
<html ng-app="openbciVisualizer">
|
||||
<head>
|
||||
<title>OpenBCI Visualizer</title>
|
||||
|
||||
<link rel="stylesheet" href="//cdn.jsdelivr.net/angular.chartjs/latest/angular-chart.css">
|
||||
|
||||
<script src="node_modules/socket.io-client/socket.io.js"></script>
|
||||
|
||||
<script src="/node_modules/angular/angular.js"></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/Chart.js/1.1.1/Chart.js"></script>
|
||||
<script src="//cdn.jsdelivr.net/angular.chartjs/latest/angular-chart.min.js"></script>
|
||||
|
||||
<script src="visualizer.js"></script>
|
||||
|
||||
<script src="lib/EEGSpectrumUtils.js"></script>
|
||||
|
||||
<script src="components/frequency/frequency.js"></script>
|
||||
<script src="components/time/time.js"></script>
|
||||
<script src="components/alpha/alpha.js"></script>
|
||||
<script src="components/delta/delta.js"></script>
|
||||
<script src="components/beta/beta.js"></script>
|
||||
<script src="components/theta/theta.js"></script>
|
||||
|
||||
<style>
|
||||
@import url(https://fonts.googleapis.com/css?family=Roboto:400,700,300);
|
||||
body {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
}
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<main>
|
||||
|
||||
<h1>OpenBCI Visualizer</h1>
|
||||
|
||||
<bci-time></bci-time>
|
||||
<bci-frequency></bci-frequency>
|
||||
|
||||
<section class="flex">
|
||||
<bci-alpha></bci-alpha>
|
||||
<bci-delta></bci-delta>
|
||||
<bci-beta></bci-beta>
|
||||
<bci-theta></bci-theta>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,28 +0,0 @@
|
||||
|
||||
var EEGSpectrumUtils = {
|
||||
|
||||
/**
|
||||
* filterBand: Give spectrums and labels, it filters the spectrums based on the labels within the range
|
||||
* @param spectrums
|
||||
* @param labels
|
||||
* @param range
|
||||
* @returns {{spectrums: Array, labels: *}}
|
||||
*/
|
||||
filterBand: function (spectrums, labels, range) {
|
||||
//if (!spectrums ) return console.log();
|
||||
spectrums = spectrums.map(function (channel) {
|
||||
return channel.filter(function (spectrum, index) {
|
||||
return labels[index] >= range[0] && labels[index] <= range[1];
|
||||
});
|
||||
});
|
||||
spectrums = [spectrums.map(function (channel) {
|
||||
return channel.reduce(function (a, b) {
|
||||
return a + b;
|
||||
}) / channel.length;
|
||||
})];
|
||||
return {
|
||||
spectrums: spectrums,
|
||||
labels: labels
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,14 +0,0 @@
|
||||
|
||||
angular.module('openbciVisualizer', ['chart.js'])
|
||||
.config(function (ChartJsProvider) {
|
||||
ChartJsProvider.setOptions({
|
||||
chartColors: ['#F7464A', '#46BFBD','#FDB45C', '#949FB1','#4D5360', '#803690','#00ADF9', '#FF0000'],
|
||||
responsive: false,
|
||||
pointDot: false,
|
||||
datasetFill: false,
|
||||
scaleOverride: true,
|
||||
scaleStartValue: -2,
|
||||
scaleStepWidth: 1,
|
||||
scaleSteps: 6
|
||||
});
|
||||
});
|
||||
Arquivo binário não exibido.
|
Depois Largura: | Altura: | Tamanho: 356 KiB |
Arquivo binário não exibido.
|
Depois Largura: | Altura: | Tamanho: 3.6 MiB |
Arquivo binário não exibido.
|
Depois Largura: | Altura: | Tamanho: 1.9 MiB |
Arquivo binário não exibido.
|
Depois Largura: | Altura: | Tamanho: 3.6 MiB |
@@ -0,0 +1,3 @@
|
||||
export const environment = {
|
||||
production: false
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
/* jshint node: true */
|
||||
|
||||
module.exports = function(environment) {
|
||||
return {
|
||||
environment: environment,
|
||||
baseURL: '/',
|
||||
locationType: 'auto'
|
||||
};
|
||||
};
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
export const environment = {
|
||||
production: true
|
||||
};
|
||||
@@ -0,0 +1,51 @@
|
||||
/*global jasmine, __karma__, window*/
|
||||
Error.stackTraceLimit = Infinity;
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000;
|
||||
|
||||
__karma__.loaded = function () {
|
||||
};
|
||||
|
||||
var distPath = '/base/dist/';
|
||||
var appPath = distPath + 'app/';
|
||||
|
||||
function isJsFile(path) {
|
||||
return path.slice(-3) == '.js';
|
||||
}
|
||||
|
||||
function isSpecFile(path) {
|
||||
return path.slice(-8) == '.spec.js';
|
||||
}
|
||||
|
||||
function isAppFile(path) {
|
||||
return isJsFile(path) && (path.substr(0, appPath.length) == appPath);
|
||||
}
|
||||
|
||||
var allSpecFiles = Object.keys(window.__karma__.files)
|
||||
.filter(isSpecFile)
|
||||
.filter(isAppFile);
|
||||
|
||||
// Load our SystemJS configuration.
|
||||
System.config({
|
||||
baseURL: distPath
|
||||
});
|
||||
|
||||
System.import('system-config.js').then(function() {
|
||||
// Load and configure the TestComponentBuilder.
|
||||
return Promise.all([
|
||||
System.import('@angular/core/testing'),
|
||||
System.import('@angular/platform-browser-dynamic/testing')
|
||||
]).then(function (providers) {
|
||||
var testing = providers[0];
|
||||
var testingBrowser = providers[1];
|
||||
|
||||
testing.setBaseTestProviders(testingBrowser.TEST_BROWSER_DYNAMIC_PLATFORM_PROVIDERS,
|
||||
testingBrowser.TEST_BROWSER_DYNAMIC_APPLICATION_PROVIDERS);
|
||||
});
|
||||
}).then(function() {
|
||||
// Finally, load all spec files.
|
||||
// This will run the tests directly.
|
||||
return Promise.all(
|
||||
allSpecFiles.map(function (moduleName) {
|
||||
return System.import(moduleName);
|
||||
}));
|
||||
}).then(__karma__.start, __karma__.error);
|
||||
@@ -0,0 +1,42 @@
|
||||
module.exports = function (config) {
|
||||
config.set({
|
||||
basePath: '..',
|
||||
frameworks: ['jasmine'],
|
||||
plugins: [
|
||||
require('karma-jasmine'),
|
||||
require('karma-chrome-launcher')
|
||||
],
|
||||
customLaunchers: {
|
||||
// chrome setup for travis CI using chromium
|
||||
Chrome_travis_ci: {
|
||||
base: 'Chrome',
|
||||
flags: ['--no-sandbox']
|
||||
}
|
||||
},
|
||||
files: [
|
||||
{ pattern: 'dist/vendor/es6-shim/es6-shim.js', included: true, watched: false },
|
||||
{ pattern: 'dist/vendor/zone.js/dist/zone.js', included: true, watched: false },
|
||||
{ pattern: 'dist/vendor/reflect-metadata/Reflect.js', included: true, watched: false },
|
||||
{ pattern: 'dist/vendor/systemjs/dist/system-polyfills.js', included: true, watched: false },
|
||||
{ pattern: 'dist/vendor/systemjs/dist/system.src.js', included: true, watched: false },
|
||||
{ pattern: 'dist/vendor/zone.js/dist/async-test.js', included: true, watched: false },
|
||||
|
||||
{ pattern: 'config/karma-test-shim.js', included: true, watched: true },
|
||||
|
||||
// Distribution folder.
|
||||
{ pattern: 'dist/**/*', included: false, watched: true }
|
||||
],
|
||||
exclude: [
|
||||
// Vendor packages might include spec files. We don't want to use those.
|
||||
'dist/vendor/**/*.spec.js'
|
||||
],
|
||||
preprocessors: {},
|
||||
reporters: ['progress'],
|
||||
port: 9876,
|
||||
colors: true,
|
||||
logLevel: config.LOG_INFO,
|
||||
autoWatch: true,
|
||||
browsers: ['Chrome'],
|
||||
singleRun: false
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
/*global jasmine */
|
||||
var SpecReporter = require('jasmine-spec-reporter');
|
||||
|
||||
exports.config = {
|
||||
allScriptsTimeout: 11000,
|
||||
specs: [
|
||||
'../e2e/**/*.e2e.ts'
|
||||
],
|
||||
capabilities: {
|
||||
'browserName': 'chrome'
|
||||
},
|
||||
directConnect: true,
|
||||
baseUrl: 'http://localhost:4200/',
|
||||
framework: 'jasmine',
|
||||
jasmineNodeOpts: {
|
||||
showColors: true,
|
||||
defaultTimeoutInterval: 30000,
|
||||
print: function() {}
|
||||
},
|
||||
useAllAngular2AppRoots: true,
|
||||
beforeLaunch: function() {
|
||||
require('ts-node').register({
|
||||
project: 'e2e'
|
||||
});
|
||||
},
|
||||
onPrepare: function() {
|
||||
jasmine.getEnv().addReporter(new SpecReporter());
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
import { ClitestPage } from './app.po';
|
||||
|
||||
describe('clitest App', function() {
|
||||
let page: ClitestPage;
|
||||
|
||||
beforeEach(() => {
|
||||
page = new ClitestPage();
|
||||
})
|
||||
|
||||
it('should display message saying app works', () => {
|
||||
page.navigateTo();
|
||||
expect(page.getParagraphText()).toEqual('clitest works!');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,9 @@
|
||||
export class ClitestPage {
|
||||
navigateTo() {
|
||||
return browser.get('/');
|
||||
}
|
||||
|
||||
getParagraphText() {
|
||||
return element(by.css('clitest-app h1')).getText();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"declaration": false,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"mapRoot": "",
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"noEmitOnError": true,
|
||||
"noImplicitAny": false,
|
||||
"rootDir": ".",
|
||||
"sourceMap": true,
|
||||
"sourceRoot": "/",
|
||||
"target": "es5"
|
||||
}
|
||||
}
|
||||
externo
+1
@@ -0,0 +1 @@
|
||||
/// <reference path="../typings/main.d.ts" />
|
||||
+54
-19
@@ -1,27 +1,62 @@
|
||||
{
|
||||
"name": "openbci-visualizer",
|
||||
"private": false,
|
||||
"version": "0.0.1",
|
||||
"description": "A fullstack javascript app for capturing and visualizing OpenBCI EEG data",
|
||||
"main": "visualizer.js",
|
||||
"name": "clitest",
|
||||
"version": "0.0.0",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"openbci",
|
||||
"fft",
|
||||
"chartjs"
|
||||
],
|
||||
"author": "Alex Castillo",
|
||||
"scripts": {},
|
||||
"angular-cli": {},
|
||||
"scripts": {
|
||||
"start": "ng server",
|
||||
"postinstall": "typings install",
|
||||
"lint": "tslint \"src/**/*.ts\"",
|
||||
"format": "clang-format -i -style=file --glob=src/**/*.ts",
|
||||
"visualize": "concurrently \"ng serve\" \"node visualizer\" ",
|
||||
"simulate": "concurrently \"ng serve\" \"node visualizer simulate\" ",
|
||||
"pree2e": "webdriver-manager update",
|
||||
"e2e": "protractor"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"angular": "^1.5.3",
|
||||
"angular-chartjs": "0.0.5",
|
||||
"chart.js": "^2.0.0",
|
||||
"@angular/common": "2.0.0-rc.1",
|
||||
"@angular/compiler": "2.0.0-rc.1",
|
||||
"@angular/core": "2.0.0-rc.1",
|
||||
"@angular/platform-browser": "2.0.0-rc.1",
|
||||
"@angular/platform-browser-dynamic": "2.0.0-rc.1",
|
||||
"@angular/router": "2.0.0-rc.1",
|
||||
"chart.js": "^1.0.2",
|
||||
"chroma-js": "^1.1.1",
|
||||
"dsp.js": "neurojs/dsp.js",
|
||||
"es6-shim": "^0.35.0",
|
||||
"express": "^4.13.4",
|
||||
"fili": "^1.2.1",
|
||||
"jstat": "^1.5.2",
|
||||
"ng2-charts": "^1.0.3",
|
||||
"nodemon": "^1.9.1",
|
||||
"openbci-sdk": "^0.2.0",
|
||||
"socket.io": "^1.4.5",
|
||||
"socket.io-client": "^1.4.5",
|
||||
"yargs": "^4.3.2"
|
||||
"openbci-sdk": "^0.3.4",
|
||||
"phaser": "^2.4.7",
|
||||
"plotly.js": "^1.10.2",
|
||||
"reflect-metadata": "0.1.3",
|
||||
"rxjs": "5.0.0-beta.6",
|
||||
"smoothie": "^1.27.0",
|
||||
"socket.io": "^1.4.6",
|
||||
"socket.io-client": "^1.4.6",
|
||||
"systemjs": "0.19.26",
|
||||
"topogrid": "^1.0.6",
|
||||
"yargs": "^4.3.2",
|
||||
"zone.js": "^0.6.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
"angular-cli": "0.0.*",
|
||||
"clang-format": "^1.0.35",
|
||||
"codelyzer": "0.0.14",
|
||||
"ember-cli-inject-live-reload": "^1.4.0",
|
||||
"jasmine-core": "^2.4.1",
|
||||
"jasmine-spec-reporter": "^2.4.0",
|
||||
"karma": "^0.13.15",
|
||||
"karma-chrome-launcher": "^0.2.3",
|
||||
"karma-jasmine": "^0.3.8",
|
||||
"protractor": "^3.3.0",
|
||||
"ts-node": "^0.5.5",
|
||||
"tslint": "^3.6.0",
|
||||
"typescript": "^1.8.10",
|
||||
"typings": "^0.8.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
@import url(https://fonts.googleapis.com/css?family=Roboto:400,700,300);
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:host {
|
||||
display: block;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
padding: 20px;
|
||||
font-weight: 300;
|
||||
margin: 0;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.capitalize {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.loading:after {
|
||||
content: 'Loading...';
|
||||
text-transform: uppercase;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin: -10px 0 0 -40px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.block {
|
||||
display: block;
|
||||
margin: 20px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 0 5px rgba(0,0,0,0.3);
|
||||
background-color: #333333;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.block h2 {
|
||||
position: absolute;
|
||||
margin: 0;
|
||||
top: 10px;
|
||||
right: 20px;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.block-25 {
|
||||
width: 25%;
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.block-33 {
|
||||
width: 33%;
|
||||
min-height: 260px;
|
||||
}
|
||||
|
||||
.block-50 {
|
||||
width: 50%;
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.block-75 {
|
||||
width: 75%;
|
||||
min-height: 580px;
|
||||
}
|
||||
|
||||
.block-100 {
|
||||
width: 100%;
|
||||
min-height: 580px;
|
||||
}
|
||||
|
||||
|
||||
nav {
|
||||
margin-left: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
|
||||
nav a {
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: #ffffff;
|
||||
padding: 8px 20px;
|
||||
text-transform: uppercase;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
font-weight: 300;
|
||||
font-size: 12px;
|
||||
letter-spacing: 1px;
|
||||
margin-right: 2px;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
nav a:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
nav a:nth-child(1) {
|
||||
background-color: rgba(112,185,252,1);
|
||||
}
|
||||
|
||||
nav a:nth-child(2) {
|
||||
background-color: rgba(138,219,229,1);
|
||||
}
|
||||
|
||||
nav a:nth-child(3) {
|
||||
background-color: rgba(162,86,178,1);
|
||||
}
|
||||
|
||||
nav a:nth-child(4) {
|
||||
background-color: rgba(144,132,246,1);
|
||||
}
|
||||
|
||||
nav a:nth-child(5) {
|
||||
background-color: rgba(232,223,133,1);
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
nav a:nth-child(6) {
|
||||
background-color: rgba(148,159,177,1);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<main>
|
||||
<h1>{{title}}</h1>
|
||||
<nav>
|
||||
<a [routerLink]="['/time-series']">Time Series</a>
|
||||
<a [routerLink]="['/frequency/line', { type: 'Line' }]">Frequency Line</a>
|
||||
<a [routerLink]="['/frequency/radar', { type: 'Radar' }]">Frequency Radar</a>
|
||||
<a [routerLink]="['/frequency/bands']">Frequency Bands</a>
|
||||
<a [routerLink]="['/topo']">Topo</a>
|
||||
<a [routerLink]="['/music-training']">Music Training</a>
|
||||
</nav>
|
||||
<router-outlet></router-outlet>
|
||||
</main>
|
||||
@@ -0,0 +1,22 @@
|
||||
import {
|
||||
beforeEachProviders,
|
||||
describe,
|
||||
expect,
|
||||
it,
|
||||
inject
|
||||
} from '@angular/core/testing';
|
||||
import { DashboardComponent } from '../app/dashboard.component';
|
||||
|
||||
beforeEachProviders(() => [DashboardComponent]);
|
||||
|
||||
describe('App: DashboardComponent', () => {
|
||||
it('should create the app',
|
||||
inject([DashboardComponent], (app: DashboardComponent) => {
|
||||
expect(app).toBeTruthy();
|
||||
}));
|
||||
|
||||
it('should have as title \'clitest works!\'',
|
||||
inject([DashboardComponent], (app: DashboardComponent) => {
|
||||
expect(app.title).toEqual('clitest works!');
|
||||
}));
|
||||
});
|
||||
@@ -0,0 +1,37 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { TimeSeriesComponent } from './time-series';
|
||||
import { FrequencyComponent } from './frequency';
|
||||
import { FrequencyBandsComponent } from './frequency-bands';
|
||||
import { TopoComponent } from './topo';
|
||||
import { MusicTrainingComponent } from './music-training';
|
||||
import { Routes, Router, ROUTER_PROVIDERS, ROUTER_DIRECTIVES } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'bci-dashboard',
|
||||
templateUrl: 'dashboard.component.html',
|
||||
styleUrls: ['dashboard.component.css'],
|
||||
directives: [ROUTER_DIRECTIVES],
|
||||
providers: [ROUTER_PROVIDERS]
|
||||
})
|
||||
|
||||
@Routes([
|
||||
{ path: '/', component: MusicTrainingComponent },
|
||||
{ path: '/time-series', component: TimeSeriesComponent },
|
||||
{ path: '/frequency/line', component: FrequencyComponent },
|
||||
{ path: '/frequency/radar', component: FrequencyComponent },
|
||||
{ path: '/frequency/bands', component: FrequencyBandsComponent },
|
||||
{ path: '/topo', component: TopoComponent },
|
||||
{ path: '/music-training', component: MusicTrainingComponent }
|
||||
])
|
||||
|
||||
export class DashboardComponent implements OnInit {
|
||||
title = 'BCI Dashboard';
|
||||
|
||||
constructor (private router: Router) {
|
||||
}
|
||||
|
||||
ngOnInit () {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// The file for the current environment will overwrite this one during build
|
||||
// Different environments can be found in config/environment.{dev|prod}.ts
|
||||
// The build system defaults to the dev environment
|
||||
|
||||
export const environment = {
|
||||
production: false
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
|
||||
:host {
|
||||
display: inline-block;
|
||||
margin: 20px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 0 5px rgba(0,0,0,0.3);
|
||||
background-color: #333333;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 27%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
h2 {
|
||||
position: absolute;
|
||||
margin: 0;
|
||||
top: 10px;
|
||||
right: 20px;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.capitalize {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<section class="frequency-band" [ngClass]="{ 'loading': !data }">
|
||||
<h2 class="capitalize">{{ band }} {{ type }}</h2>
|
||||
<base-chart class="chart"
|
||||
[data]="data"
|
||||
[labels]="channels"
|
||||
[options]="options"
|
||||
[colours]="colors"
|
||||
[series]="channels"
|
||||
[chartType]="type">
|
||||
</base-chart>
|
||||
</section>
|
||||
@@ -0,0 +1,46 @@
|
||||
import {
|
||||
beforeEach,
|
||||
beforeEachProviders,
|
||||
describe,
|
||||
expect,
|
||||
it,
|
||||
inject,
|
||||
} from '@angular/core/testing';
|
||||
import { ComponentFixture, TestComponentBuilder } from '@angular/compiler/testing';
|
||||
import { Component } from '@angular/core';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { FrequencyBandComponent } from './frequency-band.component';
|
||||
|
||||
describe('Component: FrequencyBand', () => {
|
||||
let builder: TestComponentBuilder;
|
||||
|
||||
beforeEachProviders(() => [FrequencyBandComponent]);
|
||||
beforeEach(inject([TestComponentBuilder], function (tcb: TestComponentBuilder) {
|
||||
builder = tcb;
|
||||
}));
|
||||
|
||||
it('should inject the component', inject([FrequencyBandComponent],
|
||||
(component: FrequencyBandComponent) => {
|
||||
expect(component).toBeTruthy();
|
||||
}));
|
||||
|
||||
it('should create the component', inject([], () => {
|
||||
return builder.createAsync(FrequencyBandComponentTestController)
|
||||
.then((fixture: ComponentFixture<any>) => {
|
||||
let query = fixture.debugElement.query(By.directive(FrequencyBandComponent));
|
||||
expect(query).toBeTruthy();
|
||||
expect(query.componentInstance).toBeTruthy();
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
@Component({
|
||||
selector: 'test',
|
||||
template: `
|
||||
<bci-frequency-band></bci-frequency-band>
|
||||
`,
|
||||
directives: [FrequencyBandComponent]
|
||||
})
|
||||
class FrequencyBandComponentTestController {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import { Component, OnInit, OnDestroy, Input } from '@angular/core';
|
||||
import * as io from 'socket.io-client';
|
||||
import { ChartService } from '../shared';
|
||||
import { CHART_DIRECTIVES } from '../shared/ng2-charts';
|
||||
import { Constants } from '../shared/constants';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'bci-frequency-band',
|
||||
templateUrl: 'frequency-band.component.html',
|
||||
styleUrls: ['frequency-band.component.css'],
|
||||
directives: [CHART_DIRECTIVES],
|
||||
providers: [ChartService, Constants]
|
||||
})
|
||||
|
||||
export class FrequencyBandComponent implements OnInit {
|
||||
|
||||
socket: any;
|
||||
constructor(private chartService: ChartService, private constants: Constants) {
|
||||
this.socket = io(constants.socket.url);
|
||||
}
|
||||
|
||||
@Input() public type:string;
|
||||
@Input() public band:string;
|
||||
@Input() public color:number;
|
||||
|
||||
private data:Array<any> = [[]];
|
||||
private colors:Array<any>;
|
||||
private channels:Array<string> = this.chartService.getChannels();
|
||||
private options:any = this.chartService.getChartJSDefaults({
|
||||
responsive: false
|
||||
});
|
||||
|
||||
ngOnInit() {
|
||||
this.colors = this.chartService.getColorByIndex(this.color);
|
||||
this.socket.on(this.constants.socket.events.fft, (data) => {
|
||||
this.data = data[this.band || 'data'];
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy () {
|
||||
this.socket.removeListener(this.constants.socket.events.fft);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { FrequencyBandComponent } from './frequency-band.component';
|
||||
@@ -0,0 +1,9 @@
|
||||
<section>
|
||||
<bci-frequency-band [type]="'Bar'" [band]="'delta'" [color]="1"></bci-frequency-band>
|
||||
<bci-frequency-band [type]="'Bar'" [band]="'theta'" [color]="2"></bci-frequency-band>
|
||||
</section>
|
||||
<section>
|
||||
<bci-frequency-band [type]="'Bar'" [band]="'alpha'" [color]="3"></bci-frequency-band>
|
||||
<bci-frequency-band [type]="'Bar'" [band]="'beta'" [color]="4"></bci-frequency-band>
|
||||
<bci-frequency-band [type]="'Bar'" [band]="'gamma'" [color]="5"></bci-frequency-band>
|
||||
</section>
|
||||
@@ -0,0 +1,46 @@
|
||||
import {
|
||||
beforeEach,
|
||||
beforeEachProviders,
|
||||
describe,
|
||||
expect,
|
||||
it,
|
||||
inject,
|
||||
} from '@angular/core/testing';
|
||||
import { ComponentFixture, TestComponentBuilder } from '@angular/compiler/testing';
|
||||
import { Component } from '@angular/core';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { FrequencyBandsComponent } from './frequency-bands.component';
|
||||
|
||||
describe('Component: FrequencyBands', () => {
|
||||
let builder: TestComponentBuilder;
|
||||
|
||||
beforeEachProviders(() => [FrequencyBandsComponent]);
|
||||
beforeEach(inject([TestComponentBuilder], function (tcb: TestComponentBuilder) {
|
||||
builder = tcb;
|
||||
}));
|
||||
|
||||
it('should inject the component', inject([FrequencyBandsComponent],
|
||||
(component: FrequencyBandsComponent) => {
|
||||
expect(component).toBeTruthy();
|
||||
}));
|
||||
|
||||
it('should create the component', inject([], () => {
|
||||
return builder.createAsync(FrequencyBandsComponentTestController)
|
||||
.then((fixture: ComponentFixture<any>) => {
|
||||
let query = fixture.debugElement.query(By.directive(FrequencyBandsComponent));
|
||||
expect(query).toBeTruthy();
|
||||
expect(query.componentInstance).toBeTruthy();
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
@Component({
|
||||
selector: 'test',
|
||||
template: `
|
||||
<bci-frequency-bands></bci-frequency-bands>
|
||||
`,
|
||||
directives: [FrequencyBandsComponent]
|
||||
})
|
||||
class FrequencyBandsComponentTestController {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { FrequencyBandComponent } from '../frequency-band';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'bci-frequency-bands',
|
||||
templateUrl: 'frequency-bands.component.html',
|
||||
styleUrls: ['frequency-bands.component.css'],
|
||||
directives: [FrequencyBandComponent]
|
||||
})
|
||||
|
||||
export class FrequencyBandsComponent implements OnInit {
|
||||
|
||||
constructor() {}
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { FrequencyBandsComponent } from './frequency-bands.component';
|
||||
@@ -0,0 +1,23 @@
|
||||
|
||||
.chart {
|
||||
display: block;
|
||||
}
|
||||
|
||||
:host {
|
||||
display: block;
|
||||
margin: 20px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 0 5px rgba(0,0,0,0.3);
|
||||
background-color: #333333;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
h2 {
|
||||
position: absolute;
|
||||
margin: 0;
|
||||
top: 10px;
|
||||
right: 20px;
|
||||
font-weight: 300;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<section class="frequency" [ngClass]="{ 'loading': !data }">
|
||||
<h2>Frequency {{ type }}</h2>
|
||||
<base-chart class="chart"
|
||||
[data]="data"
|
||||
[labels]="labels"
|
||||
[options]="options"
|
||||
[colours]="colors"
|
||||
[series]="channels"
|
||||
[chartType]="type">
|
||||
</base-chart>
|
||||
</section>
|
||||
@@ -0,0 +1,46 @@
|
||||
import {
|
||||
beforeEach,
|
||||
beforeEachProviders,
|
||||
describe,
|
||||
expect,
|
||||
it,
|
||||
inject,
|
||||
} from '@angular/core/testing';
|
||||
import { ComponentFixture, TestComponentBuilder } from '@angular/compiler/testing';
|
||||
import { Component } from '@angular/core';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { FrequencyComponent } from './frequency.component';
|
||||
|
||||
describe('Component: Frequency', () => {
|
||||
let builder: TestComponentBuilder;
|
||||
|
||||
beforeEachProviders(() => [FrequencyComponent]);
|
||||
beforeEach(inject([TestComponentBuilder], function (tcb: TestComponentBuilder) {
|
||||
builder = tcb;
|
||||
}));
|
||||
|
||||
it('should inject the component', inject([FrequencyComponent],
|
||||
(component: FrequencyComponent) => {
|
||||
expect(component).toBeTruthy();
|
||||
}));
|
||||
|
||||
it('should create the component', inject([], () => {
|
||||
return builder.createAsync(FrequencyComponentTestController)
|
||||
.then((fixture: ComponentFixture<any>) => {
|
||||
let query = fixture.debugElement.query(By.directive(FrequencyComponent));
|
||||
expect(query).toBeTruthy();
|
||||
expect(query.componentInstance).toBeTruthy();
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
@Component({
|
||||
selector: 'test',
|
||||
template: `
|
||||
<bci-frequency></bci-frequency>
|
||||
`,
|
||||
directives: [FrequencyComponent]
|
||||
})
|
||||
class FrequencyComponentTestController {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import { Component, ElementRef, OnInit, OnDestroy, Input } from '@angular/core';
|
||||
import { RouteSegment, ROUTER_PROVIDERS } from '@angular/router';
|
||||
import * as io from 'socket.io-client';
|
||||
import { ChartService } from '../shared';
|
||||
import { CHART_DIRECTIVES } from '../shared/ng2-charts';
|
||||
import { Constants } from '../shared/constants';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'bci-frequency',
|
||||
templateUrl: 'frequency.component.html',
|
||||
styleUrls: ['frequency.component.css'],
|
||||
directives: [CHART_DIRECTIVES],
|
||||
providers: [ChartService, Constants, ROUTER_PROVIDERS]
|
||||
})
|
||||
|
||||
export class FrequencyComponent implements OnInit {
|
||||
|
||||
socket: any;
|
||||
constructor(private chartService: ChartService,
|
||||
private segment: RouteSegment,
|
||||
private constants: Constants) {
|
||||
this.socket = io(constants.socket.url);
|
||||
this.type = segment.getParam('type') || 'Line';
|
||||
}
|
||||
|
||||
@Input() type:string;
|
||||
|
||||
private data:Array<any> = [[]];
|
||||
private labels:Array<any> = [];
|
||||
private colors:Array<any> = this.chartService.getColors();
|
||||
private channels:Array<string> = this.chartService.getChannels();
|
||||
private options:any = this.chartService.getChartJSDefaults();
|
||||
|
||||
ngOnInit() {
|
||||
this.socket.on(this.constants.socket.events.fft, (data) => {
|
||||
this.data = data.data;
|
||||
this.labels = data.labels;
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy () {
|
||||
this.socket.removeListener(this.constants.socket.events.fft);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { FrequencyComponent } from './frequency.component';
|
||||
@@ -0,0 +1,2 @@
|
||||
export {environment} from './environment';
|
||||
export {DashboardComponent} from './dashboard.component';
|
||||
@@ -0,0 +1 @@
|
||||
export { MusicTrainingComponent } from './music-training.component';
|
||||
@@ -0,0 +1,20 @@
|
||||
|
||||
:host {
|
||||
display: block;
|
||||
margin: 20px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 0 5px rgba(0,0,0,0.3);
|
||||
background-color: #333333;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 70%;
|
||||
}
|
||||
|
||||
h2 {
|
||||
position: absolute;
|
||||
margin: 0;
|
||||
top: 10px;
|
||||
right: 20px;
|
||||
font-weight: 300;
|
||||
z-index: 1;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<section class="music" [ngClass]="{ 'loading': !data }">
|
||||
<h2>Music Training</h2>
|
||||
<div id="phaser"></div>
|
||||
</section>
|
||||
@@ -0,0 +1,46 @@
|
||||
import {
|
||||
beforeEach,
|
||||
beforeEachProviders,
|
||||
describe,
|
||||
expect,
|
||||
it,
|
||||
inject,
|
||||
} from '@angular/core/testing';
|
||||
import { ComponentFixture, TestComponentBuilder } from '@angular/compiler/testing';
|
||||
import { Component } from '@angular/core';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { MusicTrainingComponent } from './music-training.component';
|
||||
|
||||
describe('Component: MusicTraining', () => {
|
||||
let builder: TestComponentBuilder;
|
||||
|
||||
beforeEachProviders(() => [MusicTrainingComponent]);
|
||||
beforeEach(inject([TestComponentBuilder], function (tcb: TestComponentBuilder) {
|
||||
builder = tcb;
|
||||
}));
|
||||
|
||||
it('should inject the component', inject([MusicTrainingComponent],
|
||||
(component: MusicTrainingComponent) => {
|
||||
expect(component).toBeTruthy();
|
||||
}));
|
||||
|
||||
it('should create the component', inject([], () => {
|
||||
return builder.createAsync(MusicTrainingComponentTestController)
|
||||
.then((fixture: ComponentFixture<any>) => {
|
||||
let query = fixture.debugElement.query(By.directive(MusicTrainingComponent));
|
||||
expect(query).toBeTruthy();
|
||||
expect(query.componentInstance).toBeTruthy();
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
@Component({
|
||||
selector: 'test',
|
||||
template: `
|
||||
<bci-music-training></bci-music-training>
|
||||
`,
|
||||
directives: [MusicTrainingComponent]
|
||||
})
|
||||
class MusicTrainingComponentTestController {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import { Component, OnInit, ElementRef } from '@angular/core';
|
||||
import * as io from 'socket.io-client';
|
||||
import { Constants } from '../shared/constants';
|
||||
|
||||
declare var Phaser: any;
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'bci-music-training',
|
||||
templateUrl: 'music-training.component.html',
|
||||
styleUrls: ['music-training.component.css'],
|
||||
providers: [Constants]
|
||||
})
|
||||
export class MusicTrainingComponent implements OnInit {
|
||||
|
||||
game: any;
|
||||
phaserElement: any;
|
||||
socket: any;
|
||||
constructor(private view: ElementRef, private constants: Constants) {
|
||||
this.socket = io(constants.socket.url);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.phaserElement = this.view.nativeElement.querySelector('#phaser');
|
||||
|
||||
this.game = new Phaser.Game(480, 480, Phaser.WEBGL, this.phaserElement.id, {
|
||||
preload: this.preload, create: this.create, update: this.update
|
||||
});
|
||||
|
||||
this.socket.on(this.constants.socket.events.time, (data) => {
|
||||
console.log('data from music component', data);
|
||||
});
|
||||
}
|
||||
|
||||
preload () {
|
||||
this.game.scale.scaleMode = Phaser.ScaleManager.NO_SCALE;
|
||||
this.game.stage.backgroundColor = '#FF0000';
|
||||
}
|
||||
|
||||
create () {
|
||||
this.game.physics.startSystem(Phaser.Physics.ARCADE);
|
||||
}
|
||||
|
||||
update () {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import {
|
||||
beforeEachProviders,
|
||||
it,
|
||||
describe,
|
||||
expect,
|
||||
inject
|
||||
} from '@angular/core/testing';
|
||||
import { ChartService } from './chart.service';
|
||||
|
||||
describe('Chart Service', () => {
|
||||
beforeEachProviders(() => [ChartService]);
|
||||
|
||||
it('should ...',
|
||||
inject([ChartService], (service: ChartService) => {
|
||||
expect(service).toBeTruthy();
|
||||
}));
|
||||
});
|
||||
@@ -0,0 +1,67 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
@Injectable()
|
||||
export class ChartService {
|
||||
|
||||
constructor() {}
|
||||
|
||||
getChartJSDefaults (overrides: any = {}): any {
|
||||
return Object.assign({
|
||||
responsive: true,
|
||||
animation: false,
|
||||
animationSteps: 15,
|
||||
datasetStrokeWidth: 1,
|
||||
pointDot: false,
|
||||
pointDotRadius: 1,
|
||||
pointDotStrokeWidth: 0,
|
||||
datasetFill: false,
|
||||
scaleOverride: true,
|
||||
scaleStartValue: -2,
|
||||
scaleStepWidth: 1,
|
||||
scaleSteps: 6,
|
||||
barShowStroke: false,
|
||||
barValueSpacing: 1,
|
||||
barStrokeWidth: 1
|
||||
}, overrides);
|
||||
}
|
||||
|
||||
getChartSmoothieDefaults (overrides: any = {}): any {
|
||||
return Object.assign({
|
||||
millisPerLine: 3000,
|
||||
grid: {
|
||||
fillStyle: '#333333',
|
||||
strokeStyle: 'rgba(0,0,0,0.1)',
|
||||
sharpLines: false,
|
||||
verticalSections: this.getChannels().length,
|
||||
borderVisible: true
|
||||
},
|
||||
labels: {
|
||||
disabled: true
|
||||
},
|
||||
maxValue: this.getChannels().length * 2,
|
||||
minValue: 0
|
||||
}, overrides);
|
||||
}
|
||||
|
||||
getChannels (): Array<string> {
|
||||
return Array(8).fill('CH').map((item, index) => item + (index + 1));
|
||||
}
|
||||
|
||||
getColors (): Array<any> {
|
||||
return [
|
||||
{ strokeColor: 'rgba(112,185,252,1)', fillColor: 'rgba(112,185,252,1)' },
|
||||
{ strokeColor: 'rgba(116,150,161,1)', fillColor: 'rgba(116,150,161,1)' },
|
||||
{ strokeColor: 'rgba(162,86,178,1)', fillColor: 'rgba(162,86,178,1)' },
|
||||
{ strokeColor: 'rgba(144,132,246,1)', fillColor: 'rgba(144,132,246,1)' },
|
||||
{ strokeColor: 'rgba(138,219,229,1)', fillColor: 'rgba(138,219,229,1)' },
|
||||
{ strokeColor: 'rgba(232,223,133,1)', fillColor: 'rgba(232,223,133,1)' },
|
||||
{ strokeColor: 'rgba(148,159,177,1)', fillColor: 'rgba(148,159,177,1)' },
|
||||
{ strokeColor: 'rgba(77,83,96,1)', fillColor: 'rgba(77,83,96,1)' }
|
||||
];
|
||||
}
|
||||
|
||||
getColorByIndex (index:number): Array<any> {
|
||||
return this.getColors().filter((c, i) => index === i);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
@Injectable()
|
||||
export class Constants {
|
||||
socket: any;
|
||||
|
||||
constructor(){
|
||||
this.socket = {
|
||||
url: 'http://localhost:8080',
|
||||
events: {
|
||||
fft: 'bci:fft',
|
||||
time: 'bci:time',
|
||||
topo: 'bci:topo'
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './chart.service';
|
||||
export * from './ng2-charts';
|
||||
@@ -0,0 +1,265 @@
|
||||
import {
|
||||
Component, OnDestroy, OnInit, OnChanges,
|
||||
EventEmitter, ElementRef, Input
|
||||
} from '@angular/core';
|
||||
import {CORE_DIRECTIVES, FORM_DIRECTIVES, NgClass} from '@angular/common';
|
||||
|
||||
declare var Chart:any;
|
||||
|
||||
@Component({
|
||||
selector: 'chart',
|
||||
template: `<canvas></canvas>`,
|
||||
directives: [CORE_DIRECTIVES, NgClass]
|
||||
})
|
||||
export class ChartsComponent {}
|
||||
|
||||
@Component({
|
||||
selector: 'base-chart',
|
||||
properties: [
|
||||
'data',
|
||||
'labels',
|
||||
'series',
|
||||
'colours',
|
||||
'chartType',
|
||||
'legend',
|
||||
'options'
|
||||
],
|
||||
events: ['chartClick', 'chartHover'],
|
||||
template: `
|
||||
<canvas style="width: 100%; height: 100%;" (click)="click($event)" (mousemove)="hover($event)"></canvas>
|
||||
`,
|
||||
directives: [CORE_DIRECTIVES, FORM_DIRECTIVES, NgClass]
|
||||
})
|
||||
export class BaseChartComponent implements OnInit, OnDestroy, OnChanges {
|
||||
@Input() public data:Array<any> = [];
|
||||
@Input() public labels:Array<any> = [];
|
||||
@Input() public options:any = {responsive: true};
|
||||
@Input() public chartType:string;
|
||||
@Input() public series:Array<any> = [];
|
||||
@Input() public colours:Array<any> = [];
|
||||
@Input() public legend:boolean;
|
||||
|
||||
private ctx:any;
|
||||
private cvs:any;
|
||||
private parent:any;
|
||||
private chart:any;
|
||||
private legendTemplate:any;
|
||||
private initFlag:boolean = false;
|
||||
private chartClick:EventEmitter<any> = new EventEmitter();
|
||||
private chartHover:EventEmitter<any> = new EventEmitter();
|
||||
private defaultsColours:Array<any> = [
|
||||
{
|
||||
fillColor: 'rgba(151,187,205,0.2)',
|
||||
strokeColor: 'rgba(151,187,205,1)',
|
||||
pointColor: 'rgba(151,187,205,1)',
|
||||
pointStrokeColor: '#fff',
|
||||
pointHighlightFill: '#fff',
|
||||
pointHighlightStroke: 'rgba(151,187,205,0.8)',
|
||||
color: 'rgba(151,187,205,1)',
|
||||
highlight: 'rgba(151,187,205,0.8)'
|
||||
}, {
|
||||
fillColor: 'rgba(220,220,220,0.2)',
|
||||
strokeColor: 'rgba(220,220,220,1)',
|
||||
pointColor: 'rgba(220,220,220,1)',
|
||||
pointStrokeColor: '#fff',
|
||||
pointHighlightFill: '#fff',
|
||||
pointHighlightStroke: 'rgba(220,220,220,0.8)',
|
||||
color: 'rgba(220,220,220,1)',
|
||||
highlight: 'rgba(220,220,220,0.8)'
|
||||
}, {
|
||||
fillColor: 'rgba(247,70,74,0.2)',
|
||||
strokeColor: 'rgba(247,70,74,1)',
|
||||
pointColor: 'rgba(247,70,74,1)',
|
||||
pointStrokeColor: '#fff',
|
||||
pointHighlightFill: '#fff',
|
||||
pointHighlightStroke: 'rgba(247,70,74,0.8)',
|
||||
color: 'rgba(247,70,74,1)',
|
||||
highlight: 'rgba(247,70,74,0.8)'
|
||||
}, {
|
||||
fillColor: 'rgba(70,191,189,0.2)',
|
||||
strokeColor: 'rgba(70,191,189,1)',
|
||||
pointColor: 'rgba(70,191,189,1)',
|
||||
pointStrokeColor: '#fff',
|
||||
pointHighlightFill: '#fff',
|
||||
pointHighlightStroke: 'rgba(70,191,189,0.8)',
|
||||
color: 'rgba(70,191,189,1)',
|
||||
highlight: 'rgba(70,191,189,0.8)'
|
||||
}, {
|
||||
fillColor: 'rgba(253,180,92,0.2)',
|
||||
strokeColor: 'rgba(253,180,92,1)',
|
||||
pointColor: 'rgba(253,180,92,1)',
|
||||
pointStrokeColor: '#fff',
|
||||
pointHighlightFill: '#fff',
|
||||
pointHighlightStroke: 'rgba(253,180,92,0.8)',
|
||||
color: 'rgba(253,180,92,1)',
|
||||
highlight: 'rgba(253,180,92,0.8)'
|
||||
}, {
|
||||
fillColor: 'rgba(148,159,177,0.2)',
|
||||
strokeColor: 'rgba(148,159,177,1)',
|
||||
pointColor: 'rgba(148,159,177,1)',
|
||||
pointStrokeColor: '#fff',
|
||||
pointHighlightFill: '#fff',
|
||||
pointHighlightStroke: 'rgba(148,159,177,0.8)',
|
||||
color: 'rgba(148,159,177,1)',
|
||||
highlight: 'rgba(148,159,177,0.8)'
|
||||
}, {
|
||||
fillColor: 'rgba(77,83,96,0.2)',
|
||||
strokeColor: 'rgba(77,83,96,1)',
|
||||
pointColor: 'rgba(77,83,96,1)',
|
||||
pointStrokeColor: '#fff',
|
||||
pointHighlightFill: '#fff',
|
||||
pointHighlightStroke: 'rgba(77,83,96,0.8)',
|
||||
color: 'rgba(77,83,96,1)',
|
||||
highlight: 'rgba(77,83,96,0.8)'
|
||||
}];
|
||||
|
||||
private element:ElementRef;
|
||||
public constructor(element:ElementRef) {
|
||||
this.element = element;
|
||||
}
|
||||
|
||||
public ngOnInit():any {
|
||||
this.ctx = this.element.nativeElement.children[0].getContext('2d');
|
||||
this.cvs = this.element.nativeElement.children[0];
|
||||
this.parent = this.element.nativeElement;
|
||||
this.refresh();
|
||||
this.initFlag = true;
|
||||
}
|
||||
|
||||
public ngOnChanges():any {
|
||||
if (this.initFlag) {
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
public ngOnDestroy():any {
|
||||
if (this.chart) {
|
||||
this.chart.destroy();
|
||||
this.chart = void 0;
|
||||
}
|
||||
if (this.legendTemplate) {
|
||||
this.legendTemplate.destroy();
|
||||
this.legendTemplate = void 0;
|
||||
}
|
||||
}
|
||||
|
||||
public setLegend():void {
|
||||
let list = this.parent.getElementsByTagName('ul');
|
||||
if (list.length) {
|
||||
list[0].remove();
|
||||
this.parent.insertAdjacentHTML('beforeend', this.chart.generateLegend());
|
||||
} else {
|
||||
this.parent.insertAdjacentHTML('beforeend', this.chart.generateLegend());
|
||||
}
|
||||
}
|
||||
|
||||
public getColour(colour:Array<number>):any {
|
||||
return {
|
||||
fillColor: this.rgba(colour, 0.2),
|
||||
strokeColor: this.rgba(colour, 1),
|
||||
pointColor: this.rgba(colour, 1),
|
||||
pointStrokeColor: '#fff',
|
||||
pointHighlightFill: '#fff',
|
||||
pointHighlightStroke: this.rgba(colour, 0.8),
|
||||
color: this.rgba(colour, 1),
|
||||
highlight: this.rgba(colour, 0.8)
|
||||
};
|
||||
}
|
||||
|
||||
public getRandomInt(min:number, max:number):number {
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
}
|
||||
|
||||
public rgba(colour:Array<number>, alpha:number):string {
|
||||
return 'rgba(' + colour.concat(alpha).join(',') + ')';
|
||||
}
|
||||
|
||||
public click(evt:any):void {
|
||||
let atEvent = this.chart.getPointsAtEvent || this.chart.getBarsAtEvent || this.chart.getSegmentsAtEvent;
|
||||
let activePoints = atEvent.call(this.chart, evt);
|
||||
if (activePoints.length > 0) {
|
||||
let activeLabel = activePoints[0].label;
|
||||
this.chartClick.emit({activePoints: activePoints, activeLabel: activeLabel});
|
||||
}
|
||||
}
|
||||
|
||||
public hover(evt:any):void {
|
||||
let atEvent = this.chart.getPointsAtEvent || this.chart.getBarsAtEvent || this.chart.getSegmentsAtEvent;
|
||||
let activePoints = atEvent.call(this.chart, evt);
|
||||
if (activePoints.length > 0) {
|
||||
let activeLabel = activePoints[0].label;
|
||||
let activePoint = activePoints[0].value;
|
||||
this.chartHover.emit({activePoints: activePoints, activePoint: activePoint, activeLabel: activeLabel});
|
||||
}
|
||||
}
|
||||
|
||||
public getChartBuilder(ctx:any, data:Array<any>, options:any):any {
|
||||
return new Chart(ctx)[this.chartType](data, options);
|
||||
}
|
||||
|
||||
public getDataObject(label:string, value:any):any {
|
||||
if (this.chartType === 'Line'
|
||||
|| this.chartType === 'Bar'
|
||||
|| this.chartType === 'Radar') {
|
||||
return {
|
||||
label: label,
|
||||
data: value
|
||||
};
|
||||
}
|
||||
|
||||
if (this.chartType === 'Pie'
|
||||
|| this.chartType === 'Doughnut'
|
||||
|| this.chartType === 'PolarArea') {
|
||||
return {
|
||||
label: label,
|
||||
value: value
|
||||
};
|
||||
}
|
||||
|
||||
return void 0;
|
||||
}
|
||||
|
||||
public getChartData(labels:any, dataObject:any):any {
|
||||
if (this.chartType === 'Line'
|
||||
|| this.chartType === 'Bar'
|
||||
|| this.chartType === 'Radar') {
|
||||
return {
|
||||
labels: labels,
|
||||
datasets: dataObject
|
||||
};
|
||||
}
|
||||
if (this.chartType === 'Pie'
|
||||
|| this.chartType === 'Doughnut'
|
||||
|| this.chartType === 'PolarArea') {
|
||||
return dataObject;
|
||||
}
|
||||
}
|
||||
|
||||
private refresh():any {
|
||||
if (this.options.responsive && this.parent.clientHeight === 0) {
|
||||
return setTimeout(() => this.refresh(), 50);
|
||||
}
|
||||
|
||||
this.ngOnDestroy();
|
||||
let dataset:Array<any> = [];
|
||||
|
||||
for (let i = 0; i < this.data.length; i++) {
|
||||
let colourDesc:Array<number> = [this.getRandomInt(0, 255), this.getRandomInt(0, 255), this.getRandomInt(0, 255)];
|
||||
let colour = i < this.colours.length ? this.colours[i] : this.defaultsColours[i] || this.getColour(colourDesc);
|
||||
|
||||
let data:any = Object.assign(colour,
|
||||
this.getDataObject(this.series[i] || this.labels[i], this.data[i]));
|
||||
|
||||
dataset.push(data);
|
||||
}
|
||||
|
||||
let data:any = this.getChartData(this.labels, dataset);
|
||||
this.chart = this.getChartBuilder(this.ctx, data, this.options);
|
||||
|
||||
if (this.legend) {
|
||||
this.setLegend();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const CHART_DIRECTIVES:Array<any> = [ChartsComponent, BaseChartComponent];
|
||||
@@ -0,0 +1 @@
|
||||
export { TimeSeriesComponent } from './time-series.component';
|
||||
@@ -0,0 +1,82 @@
|
||||
|
||||
:host {
|
||||
display: block;
|
||||
margin: 20px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 0 5px rgba(0,0,0,0.3);
|
||||
background-color: #333333;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 1060px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
h2 {
|
||||
position: absolute;
|
||||
margin: 0;
|
||||
top: 10px;
|
||||
right: 20px;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.time-series canvas {
|
||||
margin: 40px 80px 40px 40px;
|
||||
}
|
||||
|
||||
.time-series-channels,
|
||||
.time-series-amplitudes {
|
||||
position: absolute;
|
||||
top: 60px;
|
||||
height: 450px;
|
||||
color: rgba(102,102,102,1);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.time-series-channels {
|
||||
width: 40px;
|
||||
left: 20px;
|
||||
text-align: left;
|
||||
color: #000000;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.time-series-amplitudes {
|
||||
right: 20px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.time-series-channels ul {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.time-series-channels li {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.time-series-channels ul,
|
||||
.time-series-amplitudes ul {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: space-around;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.time-series-duration {
|
||||
position: absolute;
|
||||
left: 50px;
|
||||
width: 98%;
|
||||
bottom: 22px;
|
||||
display: flex;
|
||||
color: rgba(102,102,102,1);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.time-series-duration time {
|
||||
width: 20%;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<section class="time-series" [ngClass]="{ 'loading': !amplitudes }">
|
||||
<h2>Time Series</h2>
|
||||
<aside class="time-series-channels">
|
||||
<ul>
|
||||
<li *ngFor="let channel of channels, let i = index"
|
||||
[ngStyle]="{ 'color': colors[i].strokeColor }">
|
||||
{{ channel }}
|
||||
</li>
|
||||
</ul>
|
||||
</aside>
|
||||
<canvas id="timeSeries" width="950" height="450"></canvas>
|
||||
<aside class="time-series-amplitudes">
|
||||
<ul>
|
||||
<li *ngFor="let amplitude of amplitudes, let i = index"
|
||||
[ngStyle]="{ 'color': colors[i].strokeColor }">
|
||||
{{ amplitude }}
|
||||
</li>
|
||||
</ul>
|
||||
</aside>
|
||||
<footer class="time-series-duration">
|
||||
<time *ngFor="let time of timeline"
|
||||
datetime="P1M">
|
||||
{{ time }}
|
||||
</time>
|
||||
</footer>
|
||||
</section>
|
||||
@@ -0,0 +1,46 @@
|
||||
import {
|
||||
beforeEach,
|
||||
beforeEachProviders,
|
||||
describe,
|
||||
expect,
|
||||
it,
|
||||
inject,
|
||||
} from '@angular/core/testing';
|
||||
import { ComponentFixture, TestComponentBuilder } from '@angular/compiler/testing';
|
||||
import { Component } from '@angular/core';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { TimeSeriesComponent } from './time-series.component';
|
||||
|
||||
describe('Component: TimeSeries', () => {
|
||||
let builder: TestComponentBuilder;
|
||||
|
||||
beforeEachProviders(() => [TimeSeriesComponent]);
|
||||
beforeEach(inject([TestComponentBuilder], function (tcb: TestComponentBuilder) {
|
||||
builder = tcb;
|
||||
}));
|
||||
|
||||
it('should inject the component', inject([TimeSeriesComponent],
|
||||
(component: TimeSeriesComponent) => {
|
||||
expect(component).toBeTruthy();
|
||||
}));
|
||||
|
||||
it('should create the component', inject([], () => {
|
||||
return builder.createAsync(TimeSeriesComponentTestController)
|
||||
.then((fixture: ComponentFixture<any>) => {
|
||||
let query = fixture.debugElement.query(By.directive(TimeSeriesComponent));
|
||||
expect(query).toBeTruthy();
|
||||
expect(query.componentInstance).toBeTruthy();
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
@Component({
|
||||
selector: 'test',
|
||||
template: `
|
||||
<bci-time-series></bci-time-series>
|
||||
`,
|
||||
directives: [TimeSeriesComponent]
|
||||
})
|
||||
class TimeSeriesComponentTestController {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
import { Component, ElementRef, OnInit, OnDestroy } from '@angular/core';
|
||||
import { SmoothieChart, TimeSeries } from 'smoothie';
|
||||
import { ChartService } from '../shared';
|
||||
import * as io from 'socket.io-client';
|
||||
import { Constants } from '../shared/constants';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'bci-time-series',
|
||||
templateUrl: 'time-series.component.html',
|
||||
styleUrls: ['time-series.component.css'],
|
||||
providers: [ChartService, Constants]
|
||||
})
|
||||
|
||||
export class TimeSeriesComponent implements OnInit {
|
||||
|
||||
socket: any;
|
||||
constructor(private view: ElementRef,
|
||||
private chartService: ChartService,
|
||||
private constants: Constants) {
|
||||
this.socket = io(constants.socket.url);
|
||||
this.chartService = chartService;
|
||||
}
|
||||
|
||||
private options = this.chartService.getChartSmoothieDefaults();
|
||||
|
||||
private timeSeries = new SmoothieChart(this.options);
|
||||
private amplitudes = [];
|
||||
private timeline = [];
|
||||
private lines = Array(8).fill(0).map(() => new TimeSeries());
|
||||
private channels = this.chartService.getChannels();
|
||||
private colors = this.chartService.getColors();
|
||||
|
||||
ngOnInit() {
|
||||
this.addTimeSeriesLines();
|
||||
|
||||
this.socket.on(this.constants.socket.events.time, (data) => {
|
||||
this.amplitudes = data.amplitudes;
|
||||
this.timeline = data.timeline;
|
||||
this.appendTimeSeriesLines(data.data);
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy () {
|
||||
this.socket.removeListener(this.constants.socket.events.time);
|
||||
}
|
||||
|
||||
ngAfterViewInit () {
|
||||
this.timeSeries.streamTo(this.view.nativeElement.querySelector('canvas'), 40);
|
||||
}
|
||||
|
||||
addTimeSeriesLines () {
|
||||
this.lines.forEach((line, index) => {
|
||||
this.timeSeries.addTimeSeries(line, {
|
||||
strokeStyle: this.colors[index].strokeColor
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
appendTimeSeriesLines (data) {
|
||||
this.lines.forEach((line, index) => {
|
||||
data[index].forEach((amplitude) => {
|
||||
line.append(new Date().getTime(), amplitude);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { TopoComponent } from './topo.component';
|
||||
@@ -0,0 +1,101 @@
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:host {
|
||||
display: block;
|
||||
margin: 20px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 0 5px rgba(0,0,0,0.3);
|
||||
background-color: #333333;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 70%;
|
||||
}
|
||||
|
||||
h2 {
|
||||
position: absolute;
|
||||
margin: 0;
|
||||
top: 10px;
|
||||
right: 20px;
|
||||
font-weight: 300;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.topoplot-wrapper {
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
border: 3px solid black;
|
||||
border-radius: 50%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
[class*='topoplot-c'] {
|
||||
z-index: 2;
|
||||
background-color: black;
|
||||
border: 1px solid black;
|
||||
width: 5%;
|
||||
height: 5%;
|
||||
border-radius: 50%;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.top {
|
||||
top: 2%
|
||||
}
|
||||
.middle {
|
||||
top: calc(50% - 50px);
|
||||
}
|
||||
.third {
|
||||
top: 70%;
|
||||
}
|
||||
.bottom {
|
||||
bottom: 2%;
|
||||
}
|
||||
|
||||
.top.left,
|
||||
.bottom.left {
|
||||
left: 32%;
|
||||
}
|
||||
|
||||
.top.right,
|
||||
.bottom.right {
|
||||
right: 32%;
|
||||
}
|
||||
|
||||
.middle.left {
|
||||
left: 25%;
|
||||
}
|
||||
|
||||
.middle.right {
|
||||
right: 25%;
|
||||
}
|
||||
|
||||
.third.left {
|
||||
left: 8%;
|
||||
}
|
||||
|
||||
.third.right {
|
||||
right: 8%;
|
||||
}
|
||||
|
||||
|
||||
/* Grid */
|
||||
|
||||
.topoplot-grid {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
-webkit-clip-path: circle(50%);
|
||||
border-radius: 50%;
|
||||
-webkit-filter: blur(10px);
|
||||
}
|
||||
|
||||
[class*='topoplot-u'] {
|
||||
float: left;
|
||||
/*border: 1px solid lightgray;*/
|
||||
width: 9.09%;
|
||||
height: 9.09%;
|
||||
/*background: lightblue;*/
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
<section [ngClass]="{ loading: !data }">
|
||||
<h2>Topo</h2>
|
||||
<section class="topoplot">
|
||||
<div id="topo"></div>
|
||||
</section>
|
||||
</section>
|
||||
@@ -0,0 +1,46 @@
|
||||
import {
|
||||
beforeEach,
|
||||
beforeEachProviders,
|
||||
describe,
|
||||
expect,
|
||||
it,
|
||||
inject,
|
||||
} from '@angular/core/testing';
|
||||
import { ComponentFixture, TestComponentBuilder } from '@angular/compiler/testing';
|
||||
import { Component } from '@angular/core';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { TopoComponent } from './topo.component';
|
||||
|
||||
describe('Component: Topo', () => {
|
||||
let builder: TestComponentBuilder;
|
||||
|
||||
beforeEachProviders(() => [TopoComponent]);
|
||||
beforeEach(inject([TestComponentBuilder], function (tcb: TestComponentBuilder) {
|
||||
builder = tcb;
|
||||
}));
|
||||
|
||||
it('should inject the component', inject([TopoComponent],
|
||||
(component: TopoComponent) => {
|
||||
expect(component).toBeTruthy();
|
||||
}));
|
||||
|
||||
it('should create the component', inject([], () => {
|
||||
return builder.createAsync(TopoComponentTestController)
|
||||
.then((fixture: ComponentFixture<any>) => {
|
||||
let query = fixture.debugElement.query(By.directive(TopoComponent));
|
||||
expect(query).toBeTruthy();
|
||||
expect(query.componentInstance).toBeTruthy();
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
@Component({
|
||||
selector: 'test',
|
||||
template: `
|
||||
<bci-topo></bci-topo>
|
||||
`,
|
||||
directives: [TopoComponent]
|
||||
})
|
||||
class TopoComponentTestController {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
import { Component, OnInit, OnDestroy, ElementRef } from '@angular/core';
|
||||
import * as io from 'socket.io-client';
|
||||
import { Constants } from '../shared/constants';
|
||||
|
||||
declare var chroma: any;
|
||||
declare var Plotly: any;
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'bci-topo',
|
||||
templateUrl: 'topo.component.html',
|
||||
styleUrls: ['topo.component.css'],
|
||||
providers: [Constants]
|
||||
})
|
||||
|
||||
export class TopoComponent implements OnInit {
|
||||
|
||||
socket: any;
|
||||
plotElement: any;
|
||||
|
||||
constructor(private view: ElementRef, private constants: Constants) {
|
||||
this.socket = io(constants.socket.url);
|
||||
}
|
||||
|
||||
private data: any = {
|
||||
x: [],
|
||||
y: [],
|
||||
name: 'density',
|
||||
ncontours: 15,
|
||||
colorscale: [
|
||||
[0, 'rgb(208, 0, 0)'],
|
||||
[.50, 'rgb(247, 192, 0)'],
|
||||
[.60, 'rgb(241, 255, 22)'],
|
||||
[.80, 'rgb(68, 255, 250)'],
|
||||
[.95, 'rgb(50, 0, 159)'],
|
||||
[1, 'rgb(51, 51, 51)']
|
||||
],
|
||||
reversescale: true,
|
||||
showscale: false,
|
||||
type: 'histogram2dcontour',
|
||||
line: {
|
||||
width: 1
|
||||
},
|
||||
contours: {
|
||||
//coloring: 'heatmap'
|
||||
}
|
||||
};
|
||||
|
||||
private layout: any = {
|
||||
autosize: true,
|
||||
width: 600,
|
||||
height: 450,
|
||||
bargap: 0,
|
||||
paper_bgcolor: 'rgba(0,0,0,0)',
|
||||
plot_bgcolor: 'rgba(0,0,0,0)',
|
||||
margin: { l: 0, r: 0, b: 0, t: 0 },
|
||||
xaxis: {
|
||||
autorange: true,
|
||||
showgrid: false,
|
||||
zeroline: false,
|
||||
showline: false,
|
||||
autotick: true,
|
||||
ticks: '',
|
||||
showticklabels: false
|
||||
},
|
||||
yaxis: {
|
||||
autorange: true,
|
||||
showgrid: false,
|
||||
zeroline: false,
|
||||
showline: false,
|
||||
autotick: true,
|
||||
ticks: '',
|
||||
showticklabels: false
|
||||
}
|
||||
};
|
||||
|
||||
private options: any = {
|
||||
staticPlot: true
|
||||
};
|
||||
|
||||
ngOnInit(): void {
|
||||
this.plotElement = this.view.nativeElement.querySelector('#topo');
|
||||
Plotly.newPlot(this.plotElement.id, [this.data], this.layout, this.options);
|
||||
|
||||
this.socket.on(this.constants.socket.events.topo, (data) => {
|
||||
console.log(data.plot);
|
||||
this.data.x = data.plot.x; //this.getRandomData().x
|
||||
this.data.y = data.plot.y; //this.getRandomData().y
|
||||
Plotly.redraw(this.plotElement);
|
||||
//Plotly.Plots.resize(this.plotElement);
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy (): void {
|
||||
this.socket.removeListener(this.constants.socket.events.topo);
|
||||
}
|
||||
|
||||
getRandomData (): any {
|
||||
function normal() {
|
||||
var x = 0, y = 0, rds, c;
|
||||
do {
|
||||
x = Math.random() * 2 - 1;
|
||||
y = Math.random() * 2 - 1;
|
||||
rds = x * x + y * y;
|
||||
} while (rds == 0 || rds > 1);
|
||||
c = Math.sqrt(-2 * Math.log(rds) / rds);
|
||||
return x * c;
|
||||
}
|
||||
|
||||
var N = 2000,
|
||||
a = -1,
|
||||
b = Math.random();
|
||||
|
||||
var step = (b - a) / (N - 1);
|
||||
var t = new Array(N), x = new Array(N), y = new Array(N);
|
||||
|
||||
for(var i = 0; i < N; i++){
|
||||
t[i] = a + step * i;
|
||||
x[i] = (Math.pow(t[i], 3)) + (0.3 * normal() );
|
||||
y[i] = (Math.pow(t[i], 6)) + (0.3 * normal() );
|
||||
}
|
||||
|
||||
return {
|
||||
x: x,
|
||||
y: y
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Arquivo binário não exibido.
|
Depois Largura: | Altura: | Tamanho: 5.3 KiB |
@@ -0,0 +1,51 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>BCI Dashboard</title>
|
||||
<base href="/">
|
||||
{{content-for 'head'}}
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style>
|
||||
@import url(https://fonts.googleapis.com/css?family=Roboto:400,700,300);
|
||||
body {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
font-weight: 300;
|
||||
color: #ffffff;
|
||||
background-color: #222222;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- Service worker support is disabled by default.
|
||||
Install the worker script and uncomment to enable.
|
||||
Only enable service workers in production.
|
||||
<script type="text/javascript">
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.register('/worker.js').catch(function(err) {
|
||||
console.log('Error installing service worker: ', err);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
-->
|
||||
</head>
|
||||
<body>
|
||||
<bci-dashboard>Loading...</bci-dashboard>
|
||||
|
||||
<script src="vendor/chart.js/Chart.js"></script>
|
||||
<script src="vendor/chroma-js/chroma.js"></script>
|
||||
<script src="vendor/plotly.js/dist/plotly.js"></script>
|
||||
<script src="vendor/phaser/dist/phaser.js"></script>
|
||||
<script src="vendor/es6-shim/es6-shim.js"></script>
|
||||
<script src="vendor/reflect-metadata/Reflect.js"></script>
|
||||
<script src="vendor/systemjs/dist/system.src.js"></script>
|
||||
<script src="vendor/zone.js/dist/zone.js"></script>
|
||||
|
||||
<script>
|
||||
System.import('system-config.js').then(function () {
|
||||
System.import('main');
|
||||
}).catch(console.error.bind(console));
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,9 @@
|
||||
import { bootstrap } from '@angular/platform-browser-dynamic';
|
||||
import { enableProdMode } from '@angular/core';
|
||||
import { DashboardComponent, environment } from './app/';
|
||||
|
||||
if (environment.production) {
|
||||
enableProdMode();
|
||||
}
|
||||
|
||||
bootstrap(DashboardComponent);
|
||||
@@ -0,0 +1,68 @@
|
||||
/***********************************************************************************************
|
||||
* User Configuration.
|
||||
**********************************************************************************************/
|
||||
/** Map relative paths to URLs. */
|
||||
const map: any = {
|
||||
'smoothie': 'vendor/smoothie/smoothie.js',
|
||||
'ng2-charts': 'vendor/ng2-charts/bundles/ng2-charts.js',
|
||||
'socket.io-client': 'vendor/socket.io-client/socket.io.js',
|
||||
'chroma-js': 'vendor/chroma-js/chroma.js',
|
||||
'plotly': 'vendor/plotly.js/dist/plotly.js',
|
||||
'phaser': 'vendor/phaser/dist/phaser.js'
|
||||
};
|
||||
|
||||
/** User packages configuration. */
|
||||
const packages: any = {
|
||||
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/***********************************************************************************************
|
||||
* Everything underneath this line is managed by the CLI.
|
||||
**********************************************************************************************/
|
||||
const barrels: string[] = [
|
||||
// Angular specific barrels.
|
||||
'@angular/core',
|
||||
'@angular/common',
|
||||
'@angular/compiler',
|
||||
'@angular/http',
|
||||
'@angular/router',
|
||||
'@angular/platform-browser',
|
||||
'@angular/platform-browser-dynamic',
|
||||
|
||||
// Thirdparty barrels.
|
||||
'rxjs',
|
||||
|
||||
// App specific barrels.
|
||||
'app',
|
||||
'app/shared',
|
||||
'app/frequency',
|
||||
'app/time-series',
|
||||
'app/nav',
|
||||
'app/frequency-bands',
|
||||
'app/frequency-band',
|
||||
'app/topo',
|
||||
'app/music-training',
|
||||
/** @cli-barrel */
|
||||
];
|
||||
|
||||
const cliSystemConfigPackages: any = {};
|
||||
barrels.forEach((barrelName: string) => {
|
||||
cliSystemConfigPackages[barrelName] = { main: 'index' };
|
||||
});
|
||||
|
||||
/** Type declaration for ambient System. */
|
||||
declare var System: any;
|
||||
|
||||
// Apply the CLI SystemJS configuration.
|
||||
System.config({
|
||||
map: {
|
||||
'@angular': 'vendor/@angular',
|
||||
'rxjs': 'vendor/rxjs',
|
||||
'main': 'main.js'
|
||||
},
|
||||
packages: cliSystemConfigPackages
|
||||
});
|
||||
|
||||
// Apply the user's configuration.
|
||||
System.config({ map, packages });
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"declaration": false,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"mapRoot": "",
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"noEmitOnError": true,
|
||||
"noImplicitAny": false,
|
||||
"outDir": "../dist/",
|
||||
"rootDir": ".",
|
||||
"sourceMap": true,
|
||||
"target": "es5",
|
||||
"inlineSources": true
|
||||
},
|
||||
|
||||
"files": [
|
||||
"main.ts",
|
||||
"typings.d.ts"
|
||||
]
|
||||
}
|
||||
externo
+3
@@ -0,0 +1,3 @@
|
||||
/// <reference path="../typings/browser.d.ts" />
|
||||
|
||||
declare var module: { id: string };
|
||||
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"rulesDirectory": ["node_modules/codelyzer"],
|
||||
"rules": {
|
||||
"max-line-length": [true, 100],
|
||||
"no-inferrable-types": true,
|
||||
"class-name": true,
|
||||
"comment-format": [
|
||||
true,
|
||||
"check-space"
|
||||
],
|
||||
"indent": [
|
||||
true,
|
||||
"spaces"
|
||||
],
|
||||
"eofline": true,
|
||||
"no-duplicate-variable": true,
|
||||
"no-eval": true,
|
||||
"no-arg": true,
|
||||
"no-internal-module": true,
|
||||
"no-trailing-whitespace": true,
|
||||
"no-bitwise": true,
|
||||
"no-shadowed-variable": true,
|
||||
"no-unused-expression": true,
|
||||
"no-unused-variable": true,
|
||||
"one-line": [
|
||||
true,
|
||||
"check-catch",
|
||||
"check-else",
|
||||
"check-open-brace",
|
||||
"check-whitespace"
|
||||
],
|
||||
"quotemark": [
|
||||
true,
|
||||
"single",
|
||||
"avoid-escape"
|
||||
],
|
||||
"semicolon": [true, "always"],
|
||||
"typedef-whitespace": [
|
||||
true,
|
||||
{
|
||||
"call-signature": "nospace",
|
||||
"index-signature": "nospace",
|
||||
"parameter": "nospace",
|
||||
"property-declaration": "nospace",
|
||||
"variable-declaration": "nospace"
|
||||
}
|
||||
],
|
||||
"curly": true,
|
||||
"variable-name": [
|
||||
true,
|
||||
"ban-keywords",
|
||||
"check-format",
|
||||
"allow-trailing-underscore"
|
||||
],
|
||||
"whitespace": [
|
||||
true,
|
||||
"check-branch",
|
||||
"check-decl",
|
||||
"check-operator",
|
||||
"check-separator",
|
||||
"check-type"
|
||||
],
|
||||
"component-selector-name": [true, "kebab-case"],
|
||||
"component-selector-type": [true, "element"],
|
||||
"host-parameter-decorator": true,
|
||||
"input-parameter-decorator": true,
|
||||
"output-parameter-decorator": true,
|
||||
"attribute-parameter-decorator": true,
|
||||
"input-property-directive": true,
|
||||
"output-property-directive": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"ambientDevDependencies": {
|
||||
"angular-protractor": "registry:dt/angular-protractor#1.5.0+20160425143459",
|
||||
"jasmine": "registry:dt/jasmine#2.2.0+20160412134438",
|
||||
"selenium-webdriver": "registry:dt/selenium-webdriver#2.44.0+20160317120654"
|
||||
},
|
||||
"ambientDependencies": {
|
||||
"chart": "registry:dt/chart#0.0.0+20160316155526",
|
||||
"chroma-js": "registry:dt/chroma-js#0.5.6+20160317120654",
|
||||
"es6-shim": "registry:dt/es6-shim#0.31.2+20160317120654",
|
||||
"smoothie": "registry:dt/smoothie#1.25.0+20160316155526",
|
||||
"socket.io-client": "registry:dt/socket.io-client#1.4.4+20160317120654"
|
||||
}
|
||||
}
|
||||
+224
-45
@@ -1,30 +1,19 @@
|
||||
var express = require('express');
|
||||
var app = express();
|
||||
var path = require('path');
|
||||
var http = require('http').Server(app);
|
||||
var argv = require('yargs').argv;
|
||||
var OpenBCIBoard = require('openbci-sdk');
|
||||
var dsp = require('dsp.js');
|
||||
var io = require('socket.io')(http);
|
||||
var io = require('socket.io')(process.env.app_port || 8080);
|
||||
var topogrid = require('topogrid');
|
||||
var jStat = require('jstat').jStat;
|
||||
var Fili = require('fili');
|
||||
|
||||
var globalScale = 1.5;
|
||||
|
||||
// Sockets
|
||||
io.on('connection', function(socket){
|
||||
console.log('A user connected');
|
||||
});
|
||||
|
||||
// Server
|
||||
app.use(express.static(path.join(__dirname, '/app')));
|
||||
|
||||
app.use('/node_modules', express.static(path.join(__dirname, '/node_modules')));
|
||||
|
||||
app.get('*', function(req, res) {
|
||||
res.sendFile(path.join(__dirname, '/app/index.html'));
|
||||
});
|
||||
|
||||
http.listen(3060, function () {
|
||||
console.log('listening on port 3060');
|
||||
});
|
||||
|
||||
// OpenBCI
|
||||
var board = new OpenBCIBoard.OpenBCIBoard({
|
||||
verbose: true
|
||||
@@ -34,6 +23,7 @@ board.autoFindOpenBCIBoard()
|
||||
.then(onBoardFind)
|
||||
.catch(function () {
|
||||
if (!!(argv._[0] && argv._[0] === 'simulate')) {
|
||||
globalScale = 4;
|
||||
board.connect(OpenBCIBoard.OpenBCIConstants.OBCISimulatorPortName)
|
||||
.then(onBoardConnect);
|
||||
}
|
||||
@@ -60,17 +50,79 @@ function onBoardReady () {
|
||||
|
||||
var bins = 128; // Approx .5 second
|
||||
var bufferSize = 128;
|
||||
var windowRefreshRate = 16;
|
||||
var windowRefreshRate = 8;
|
||||
var windowSize = bins / windowRefreshRate;
|
||||
var sampleRate = board.sampleRate();
|
||||
var sampleInterval = (1 / sampleRate) * 1000; // in milliseconds (4)
|
||||
var sampleNumber = 0;
|
||||
var signals = [[],[],[],[],[],[],[],[]];
|
||||
var timeSeries = [[],[],[],[],[],[],[],[]];
|
||||
|
||||
var timeSeriesWindow = 5; // in seconds
|
||||
var timeSeriesRate = 10; // emits time series every 10 samples (adds 40 ms delay because this * sampleInterval = 40
|
||||
var seriesNumber = 0;
|
||||
var timeline = generateTimeline(20, 2, 's');
|
||||
var timeSeries = new Array(8).fill([]); // 8 channels
|
||||
timeSeries = timeSeries.map(function () {
|
||||
return new Array((sampleRate * timeSeriesWindow)).fill(0).map(function (amplitude, channelNumber) {
|
||||
return offsetForGrid(amplitude, channelNumber);
|
||||
}); // / timeSeriesRate
|
||||
});
|
||||
|
||||
// the parameters for the grid [x,y,z] where x is the min of the grid, y is the
|
||||
// max of the grid and z is the number of points
|
||||
var grid_params = [0,10,11];
|
||||
var pos_x = [3,7,2,8,0,10,3,7]; // x coordinates of the data
|
||||
var pos_y = [0,0,3,3,8,8,10,10]; // y coordinates of the data
|
||||
// var data = [10,0,0,0,0,0,-10,30,25]; // the data values
|
||||
|
||||
// Instance of a filter coefficient calculator
|
||||
var iirCalculator = new Fili.CalcCascades();
|
||||
|
||||
// calculate filter coefficients
|
||||
var notchFilterCoeffs = iirCalculator.bandstop({
|
||||
order: 2, // cascade 3 biquad filters (max: 12)
|
||||
characteristic: 'butterworth',
|
||||
Fs: sampleRate, // sampling frequency
|
||||
Fc: 60,
|
||||
F1: 59,
|
||||
F2: 61,
|
||||
gain: 0, // gain for peak, lowshelf and highshelf
|
||||
preGain: false // adds one constant multiplication for highpass and lowpass
|
||||
// k = (1 + cos(omega)) * 0.5 / k = 1 with preGain == false
|
||||
});
|
||||
// create a filter instance from the calculated coeffs
|
||||
var notchFilter = new Fili.IirFilter(notchFilterCoeffs);
|
||||
|
||||
var hpFilterCoeffs = iirCalculator.highpass({
|
||||
order: 3, // cascade 3 biquad filters (max: 12)
|
||||
characteristic: 'butterworth',
|
||||
Fs: sampleRate, // sampling frequency
|
||||
Fc: 1,
|
||||
gain: 0, // gain for peak, lowshelf and highshelf
|
||||
preGain: false // adds one constant multiplication for highpass and lowpass
|
||||
// k = (1 + cos(omega)) * 0.5 / k = 1 with preGain == false
|
||||
});
|
||||
|
||||
var hpFilter = new Fili.IirFilter(hpFilterCoeffs);
|
||||
|
||||
var lpFilterCoeffs = iirCalculator.lowpass({
|
||||
order: 3, // cascade 3 biquad filters (max: 12)
|
||||
characteristic: 'butterworth',
|
||||
Fs: sampleRate, // sampling frequency
|
||||
Fc: 50,
|
||||
gain: 0, // gain for peak, lowshelf and highshelf
|
||||
preGain: false // adds one constant multiplication for highpass and lowpass
|
||||
// k = (1 + cos(omega)) * 0.5 / k = 1 with preGain == false
|
||||
});
|
||||
|
||||
var lpFilter = new Fili.IirFilter(lpFilterCoeffs);
|
||||
|
||||
function onSample (sample) {
|
||||
console.log('sample', sample);
|
||||
|
||||
sampleNumber++;
|
||||
|
||||
console.log('sample', sample);
|
||||
|
||||
Object.keys(sample.channelData).forEach(function (channel, i) {
|
||||
signals[i].push(sample.channelData[channel]);
|
||||
});
|
||||
@@ -80,30 +132,45 @@ function onSample (sample) {
|
||||
var spectrums = [[],[],[],[],[],[],[],[]];
|
||||
|
||||
signals.forEach(function (signal, index) {
|
||||
signal = notchFilter.multiStep(signal);
|
||||
var fft = new dsp.FFT(bufferSize, sampleRate);
|
||||
fft.forward(signal);
|
||||
|
||||
spectrums[index] = parseObjectAsArray(fft.spectrum);
|
||||
spectrums[index] = voltsToMicrovolts(spectrums[index], true);
|
||||
timeSeries[index] = voltsToMicrovolts(signal);
|
||||
});
|
||||
|
||||
var scaler = sampleRate / bins;
|
||||
|
||||
var labels = new Array(bins / 2).fill()
|
||||
.map(function (x, i) {
|
||||
return Math.ceil(i * scaler);
|
||||
// Apply scaler
|
||||
.map(function (label, index) {
|
||||
return Math.ceil(index * (sampleRate / bins));
|
||||
});
|
||||
|
||||
io.emit('openBCIData', {
|
||||
spectrums: {
|
||||
data: spectrums,
|
||||
labels: labels
|
||||
},
|
||||
timeSeries: {
|
||||
data: timeSeries,
|
||||
labels: new Array(128).fill(2)
|
||||
}
|
||||
|
||||
var spectrumsByBand = [];
|
||||
var bands = {
|
||||
delta: [1, 3],
|
||||
theta: [4, 8],
|
||||
alpha: [8, 12],
|
||||
beta: [13, 30],
|
||||
gamma: [30, 100]
|
||||
};
|
||||
|
||||
for (band in bands) {
|
||||
spectrumsByBand[band] = filterBand(spectrums, labels, bands[band])
|
||||
}
|
||||
|
||||
// Skip every 4, add unit
|
||||
labels = labels.map(function (label, index, labels) {
|
||||
return (index % 4 === 0 || index === (labels.length - 1)) ? label + ' Hz' : '';
|
||||
});
|
||||
|
||||
io.emit('bci:fft', {
|
||||
data: spectrums,
|
||||
theta: spectrumsByBand.theta.spectrums,
|
||||
delta: spectrumsByBand.delta.spectrums,
|
||||
alpha: spectrumsByBand.alpha.spectrums,
|
||||
beta: spectrumsByBand.beta.spectrums,
|
||||
gamma: spectrumsByBand.gamma.spectrums,
|
||||
labels: labels
|
||||
});
|
||||
|
||||
signals = signals.map(function (channel) {
|
||||
@@ -112,13 +179,87 @@ function onSample (sample) {
|
||||
});
|
||||
});
|
||||
|
||||
var meanSpectrum = spectrums.map(function(channel){
|
||||
return jStat.mean(channel);
|
||||
});
|
||||
|
||||
|
||||
sampleNumber = bins - windowSize;
|
||||
|
||||
}
|
||||
|
||||
// Time Series
|
||||
seriesNumber++;
|
||||
|
||||
timeSeries.forEach(function (channel, index) {
|
||||
channel.push(
|
||||
offsetForGrid(sample.channelData[index], index)
|
||||
);
|
||||
channel.shift();
|
||||
});
|
||||
|
||||
if (seriesNumber === timeSeriesRate) {
|
||||
|
||||
var amplitudes = signals.map(function (channel) {
|
||||
return (Math.round(voltsToMicrovolts(channel[channel.length - 1])[0])) + ' uV';
|
||||
});
|
||||
|
||||
io.emit('bci:time', {
|
||||
data: timeSeries,
|
||||
amplitudes: amplitudes,
|
||||
timeline: timeline
|
||||
});
|
||||
|
||||
seriesNumber = 0;
|
||||
|
||||
grid = topogrid.create(pos_x,pos_y,sample.channelData,grid_params);
|
||||
var grid_flat = [].concat.apply([], grid);
|
||||
|
||||
//**********//
|
||||
|
||||
var Xs = signals.map(function (channel, i) {
|
||||
var x = [3,7,2,8,0,10,3,7];
|
||||
return channel.map(function (volt) {
|
||||
return volt + (x[i]);
|
||||
});
|
||||
});
|
||||
|
||||
var Ys = signals.map(function (channel, i) {
|
||||
var y = [0,0,3,3,8,8,10,10];
|
||||
return channel.map(function (volt) {
|
||||
return volt + (y[i]);
|
||||
});
|
||||
});
|
||||
|
||||
var plotX = [].concat.apply([], Xs).sort(function (a, b) {
|
||||
return a - b;
|
||||
});
|
||||
var plotY = [].concat.apply([], Ys).sort(function (a, b) {
|
||||
return a - b;
|
||||
});
|
||||
|
||||
//**********//
|
||||
|
||||
io.emit('bci:topo', {
|
||||
data: grid_flat,
|
||||
plot: {
|
||||
x: plotX,
|
||||
y: plotY
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// @TODO: initial dataset is returning messed up values
|
||||
function offsetForGrid (amplitude, channelNumber) {
|
||||
var scaledAmplitude = amplitude * Math.pow(10, globalScale);
|
||||
var offset = 2 * (timeSeries.length - channelNumber) - 1;
|
||||
return parseFloat(scaledAmplitude + offset);
|
||||
}
|
||||
|
||||
function voltsToMicrovolts (volts, log) {
|
||||
if (!Array.isArray(volts)) volts = [volts];
|
||||
return volts.map(function (volt) {
|
||||
return log ? Math.log10(Math.pow(10, 6) * volt) : Math.pow(10, 6) * volt;
|
||||
});
|
||||
@@ -132,21 +273,59 @@ function parseObjectAsArray (obj) {
|
||||
return array;
|
||||
}
|
||||
|
||||
function filterBand(spectrums, labels, range) {
|
||||
if (!spectrums ) return console.log('Please provide spectrums');
|
||||
spectrums = spectrums.map(function (channel) {
|
||||
return channel.filter(function (spectrum, index) {
|
||||
return labels[index] >= range[0] && labels[index] <= range[1];
|
||||
});
|
||||
});
|
||||
spectrums = [spectrums.map(function (channel) {
|
||||
if (channel.length) {
|
||||
return channel.reduce(function (a, b) {
|
||||
return a + b;
|
||||
}) / channel.length;
|
||||
} else return channel;
|
||||
})];
|
||||
return {
|
||||
spectrums: spectrums,
|
||||
labels: labels
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* generateTimeline
|
||||
* @param size
|
||||
* @param skip
|
||||
* @param suffix
|
||||
* @returns {Array.<T>}
|
||||
*/
|
||||
function generateTimeline (size, skip, suffix) {
|
||||
return new Array(size)
|
||||
.fill()
|
||||
.map(function (value, index) {
|
||||
return index;
|
||||
})
|
||||
.filter(function (value, index) {
|
||||
return index % skip === 0;
|
||||
})
|
||||
.map(function (value) {
|
||||
return (value ? '-' : '') + value + suffix;
|
||||
})
|
||||
.reverse();
|
||||
}
|
||||
|
||||
/**
|
||||
* disconnectBoard
|
||||
*/
|
||||
function disconnectBoard () {
|
||||
board.streamStop()
|
||||
.then(function () {
|
||||
setTimeout(function () {
|
||||
board.disconnect().then(function () {
|
||||
console.log('board disconnected');
|
||||
process.exit();
|
||||
});
|
||||
}, 50);
|
||||
board.disconnect().then(function () {
|
||||
console.log('board disconnected');
|
||||
process.exit();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
process.on('SIGINT', function () {
|
||||
setTimeout(disconnectBoard, 50);
|
||||
});
|
||||
process.on('SIGINT', disconnectBoard);
|
||||
|
||||
Referência em uma Nova Issue
Bloquear um usuário