Comparar commits
36 Commits
angular1
...
angular2-rc6
| Autor | SHA1 | Data | |
|---|---|---|---|
| c28e6e8080 | |||
| 723eb8c533 | |||
| aa7a5c78f2 | |||
| ff0b0192bb | |||
| 2803c3b829 | |||
| 6e1fc861e7 | |||
| d96fbabef8 | |||
| ed260da6ee | |||
| 4cd9a641ce | |||
| 9601ff89a1 | |||
| 2942d8a3d0 | |||
| b3f41b00de | |||
| 8b7e60ca35 | |||
| 7661dad72e | |||
| cc3eaf834b | |||
| 8351474fbc | |||
| 005950b173 | |||
| 847c1ff511 | |||
| 44ed5c8fa5 | |||
| 44f7a035c8 | |||
| ef761dd32b | |||
| c2bad61f92 | |||
| 74c8fb1a68 | |||
| 36986b66a6 | |||
| 98ebbd83fd | |||
| 118b480690 | |||
| e3e9974888 | |||
| 0c237e0116 | |||
| bd534b3216 | |||
| 7393c0603b | |||
| 17ef476a32 | |||
| ef5857e3a2 | |||
| 92f27fb400 | |||
| 1e4401c68b | |||
| 64460c3fe6 | |||
| 4de6d83bf4 |
@@ -0,0 +1,14 @@
|
||||
# Editor configuration, see 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
|
||||
+33
-3
@@ -1,3 +1,33 @@
|
||||
.idea
|
||||
node_modules
|
||||
npm-debug.log
|
||||
# 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
|
||||
.project
|
||||
.classpath
|
||||
*.launch
|
||||
.settings/
|
||||
|
||||
# misc
|
||||
/.sass-cache
|
||||
/connect.lock
|
||||
/coverage/*
|
||||
/libpeerconnection.log
|
||||
npm-debug.log
|
||||
testem.log
|
||||
/typings
|
||||
|
||||
# e2e
|
||||
/e2e/*.js
|
||||
/e2e/*.map
|
||||
|
||||
#System Files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
+16
-4
@@ -2,7 +2,7 @@
|
||||
|
||||

|
||||
|
||||
A fullstack javascript app for capturing and visualizing OpenBCI EEG data
|
||||
A fullstack JavaScript app for reading and visualizing OpenBCI EEG data.
|
||||
|
||||
This project is under development, this is just a first draft.
|
||||
|
||||
@@ -12,18 +12,30 @@ This project is under development, this is just a first draft.
|
||||
* Turn on OpenBCI board
|
||||
|
||||
```bash
|
||||
npm install -g angular-cli concurrently
|
||||
npm install
|
||||
node start
|
||||
npm run visualize
|
||||
```
|
||||
|
||||
* Go to: http://localhost:3060
|
||||
* Go to: http://localhost:4200
|
||||
|
||||
## Simulating data
|
||||
|
||||
```bash
|
||||
node run simulate
|
||||
npm run simulate
|
||||
```
|
||||
|
||||
## Technologies
|
||||
|
||||
* Node.js
|
||||
* Angular 2
|
||||
* Socket.io
|
||||
* Data Visualization
|
||||
- Plotly.js
|
||||
- Chart.js
|
||||
- Smoothie
|
||||
|
||||
|
||||
## Support
|
||||
|
||||
Pull requests are welcomed!
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"project": {
|
||||
"version": "1.0.0-beta.11-webpack.8",
|
||||
"name": "openbci-dashboard"
|
||||
},
|
||||
"apps": [
|
||||
{
|
||||
"root": "src",
|
||||
"outDir": "dist",
|
||||
"assets": "assets",
|
||||
"index": "index.html",
|
||||
"main": "main.ts",
|
||||
"test": "test.ts",
|
||||
"tsconfig": "tsconfig.json",
|
||||
"prefix": "app",
|
||||
"mobile": false,
|
||||
"styles": [
|
||||
"styles.css"
|
||||
],
|
||||
"scripts": [],
|
||||
"environments": {
|
||||
"source": "environments/environment.ts",
|
||||
"prod": "environments/environment.prod.ts",
|
||||
"dev": "environments/environment.dev.ts"
|
||||
}
|
||||
}
|
||||
],
|
||||
"addons": [],
|
||||
"packages": [],
|
||||
"e2e": {
|
||||
"protractor": {
|
||||
"config": "./protractor.conf.js"
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"karma": {
|
||||
"config": "./karma.conf.js"
|
||||
}
|
||||
},
|
||||
"defaults": {
|
||||
"styleExt": "css",
|
||||
"prefixInterfaces": false,
|
||||
"lazyRoutePrefix": "+"
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
<section ng-class="{ loading: !$ctrl.data }">
|
||||
<h2 class="capitalize">{{ $ctrl.type }}</h2>
|
||||
<canvas id="frequency-band-{{ $ctrl.type }}"
|
||||
class="chart chart-bar"
|
||||
chart-data="$ctrl.data"
|
||||
chart-labels="$ctrl.channels"
|
||||
chart-series="$ctrl.channels"
|
||||
chart-options="$ctrl.options"
|
||||
chart-colours="$ctrl.colors">
|
||||
</canvas>
|
||||
</section>
|
||||
@@ -1,58 +0,0 @@
|
||||
|
||||
(function () {
|
||||
|
||||
var BCIFrequencyBand = {
|
||||
templateUrl: 'components/frequency-band/frequency-band.html',
|
||||
bindings: {
|
||||
type: '@',
|
||||
color: '@',
|
||||
eventName: '@'
|
||||
},
|
||||
controller: function ($timeout) {
|
||||
|
||||
var $ctrl = this;
|
||||
|
||||
var socket = io();
|
||||
|
||||
$ctrl.colors = [
|
||||
{ fillColor: 'rgba(112,185,252,1)' },
|
||||
{ fillColor: 'rgba(116,150,161,1)' },
|
||||
{ fillColor: 'rgba(162,86,178,1)' },
|
||||
{ fillColor: 'rgba(144,132,246,1)' },
|
||||
{ fillColor: 'rgba(138,219,229,1)' },
|
||||
{ fillColor: 'rgba(232,223,133,1)' },
|
||||
{ fillColor: 'rgba(148,159,177,1)' },
|
||||
{ fillColor: 'rgba(182,224,53,1)' }
|
||||
].filter(function (color, index) {
|
||||
return parseInt($ctrl.color) === index;
|
||||
});
|
||||
|
||||
console.log($ctrl.type, $ctrl.colors);
|
||||
|
||||
$ctrl.channels = ['CH1','CH2','CH3','CH4','CH5','CH6','CH7','CH8'];
|
||||
|
||||
$ctrl.options = {
|
||||
responsive: true,
|
||||
animation: true,
|
||||
animationSteps: 15
|
||||
};
|
||||
|
||||
socket.on($ctrl.eventName, function (data) {
|
||||
$timeout(function () {
|
||||
$ctrl.labels = data.labels;
|
||||
$ctrl.data = data[$ctrl.type || 'data'];
|
||||
});
|
||||
});
|
||||
|
||||
$ctrl.$onDestroy = function () {
|
||||
socket.removeListener($ctrl.eventName);
|
||||
};
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
angular
|
||||
.module('bciDashboard')
|
||||
.component('bciFrequencyBand', BCIFrequencyBand);
|
||||
|
||||
})();
|
||||
@@ -1,12 +0,0 @@
|
||||
<section ng-class="{ loading: !$ctrl.data }">
|
||||
<h2>Frequency <span class="frequency-type">{{ $ctrl.type }}</span></h2>
|
||||
<canvas id="frequency"
|
||||
class="chart-base"
|
||||
chart-type="$ctrl.type"
|
||||
chart-data="$ctrl.data"
|
||||
chart-labels="$ctrl.labels"
|
||||
chart-series="$ctrl.channels"
|
||||
chart-options="$ctrl.options"
|
||||
chart-colours="$ctrl.colors">
|
||||
</canvas>
|
||||
</section>
|
||||
@@ -1,56 +0,0 @@
|
||||
|
||||
(function () {
|
||||
|
||||
var BCIFrequency = {
|
||||
templateUrl: 'components/frequency/frequency.html',
|
||||
bindings: {
|
||||
type: '@',
|
||||
eventName: '@'
|
||||
},
|
||||
controller: function ($timeout) {
|
||||
|
||||
var $ctrl = this;
|
||||
|
||||
// Default chart type as fallback
|
||||
$ctrl.type = $ctrl.type || 'Line';
|
||||
|
||||
var socket = io();
|
||||
|
||||
$ctrl.colors = [
|
||||
{ strokeColor: 'rgba(112,185,252,1)' },
|
||||
{ strokeColor: 'rgba(116,150,161,1)' },
|
||||
{ strokeColor: 'rgba(162,86,178,1)' },
|
||||
{ strokeColor: 'rgba(144,132,246,1)' },
|
||||
{ strokeColor: 'rgba(138,219,229,1)' },
|
||||
{ strokeColor: 'rgba(232,223,133,1)' },
|
||||
{ strokeColor: 'rgba(148,159,177,1)' },
|
||||
{ strokeColor: 'rgba(182,224,53,1)' }
|
||||
];
|
||||
|
||||
$ctrl.options = {
|
||||
responsive: true,
|
||||
animation: true,
|
||||
animationSteps: 15
|
||||
};
|
||||
|
||||
$ctrl.series = ['CH1','CH2','CH3','CH4','CH5','CH6','CH7','CH8'];
|
||||
|
||||
socket.on($ctrl.eventName, function (data) {
|
||||
$timeout(function () {
|
||||
$ctrl.data = data.data;
|
||||
$ctrl.labels = data.labels;
|
||||
});
|
||||
});
|
||||
|
||||
$ctrl.$onDestroy = function () {
|
||||
socket.removeListener($ctrl.eventName);
|
||||
};
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
angular
|
||||
.module('bciDashboard')
|
||||
.component('bciFrequency', BCIFrequency);
|
||||
|
||||
})();
|
||||
@@ -1,88 +0,0 @@
|
||||
|
||||
(function () {
|
||||
|
||||
angular
|
||||
.module('bciDashboard')
|
||||
.directive('bciTimeSeries', bciTimeSeries);
|
||||
|
||||
function bciTimeSeries() {
|
||||
|
||||
var timeSeries = new SmoothieChart({
|
||||
millisPerLine: 3000,
|
||||
grid: {
|
||||
fillStyle: '#333333',
|
||||
strokeStyle: 'rgba(0,0,0,0.1)',
|
||||
sharpLines: false,
|
||||
verticalSections: 8,
|
||||
borderVisible: true
|
||||
},
|
||||
labels: {
|
||||
disabled: true
|
||||
},
|
||||
maxValue: 8 * 2,
|
||||
minValue: 0
|
||||
});
|
||||
|
||||
return {
|
||||
templateUrl: 'components/time-series/time-series.html',
|
||||
scope: {
|
||||
eventName: '@'
|
||||
},
|
||||
bindToController: true,
|
||||
controllerAs: '$ctrl',
|
||||
controller: function ($timeout) {
|
||||
|
||||
var $ctrl = this;
|
||||
|
||||
var socket = io();
|
||||
|
||||
$ctrl.colors = [
|
||||
{ strokeColor: 'rgba(112,185,252,1)' },
|
||||
{ strokeColor: 'rgba(116,150,161,1)' },
|
||||
{ strokeColor: 'rgba(162,86,178,1)' },
|
||||
{ strokeColor: 'rgba(144,132,246,1)' },
|
||||
{ strokeColor: 'rgba(138,219,229,1)' },
|
||||
{ strokeColor: 'rgba(232,223,133,1)' },
|
||||
{ strokeColor: 'rgba(148,159,177,1)' },
|
||||
{ strokeColor: 'rgba(182,224,53,1)' }
|
||||
];
|
||||
|
||||
$ctrl.channels = ['CH1','CH2','CH3','CH4','CH5','CH6','CH7','CH8'];
|
||||
|
||||
// Construct time series array with 8 lines
|
||||
var lines = Array(8).fill().map(function () {
|
||||
return new TimeSeries();
|
||||
});
|
||||
|
||||
lines.forEach(function (line, index) {
|
||||
timeSeries.addTimeSeries(line, { strokeStyle: $ctrl.colors[index].strokeColor });
|
||||
});
|
||||
|
||||
socket.on($ctrl.eventName, function (data) {
|
||||
|
||||
$timeout(function () {
|
||||
$ctrl.amplitudes = data.amplitudes;
|
||||
$ctrl.timeline = data.timeline;
|
||||
});
|
||||
|
||||
lines.forEach(function (line, index) {
|
||||
data.data[index].forEach(function (amplitude) {
|
||||
line.append(new Date().getTime(), amplitude);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
$ctrl.$onDestroy = function () {
|
||||
socket.removeListener($ctrl.eventName);
|
||||
};
|
||||
|
||||
},
|
||||
link: function (scope, element) {
|
||||
// 200 = 50 samples * 4 milliseconds (sample rate)
|
||||
timeSeries.streamTo(element[0].querySelector('canvas'), 40);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
})();
|
||||
@@ -1,19 +0,0 @@
|
||||
<section ng-class="{ loading: !$ctrl.grid }">
|
||||
<h2>Topo</h2>
|
||||
<section class="topoplot-wrapper">
|
||||
|
||||
<article class="topoplot-c1 top left"></article>
|
||||
<article class="topoplot-c2 top right"></article>
|
||||
<article class="topoplot-c3 middle left"></article>
|
||||
<article class="topoplot-c4 middle right"></article>
|
||||
<article class="topoplot-c5 third left"></article>
|
||||
<article class="topoplot-c6 third right"></article>
|
||||
<article class="topoplot-c7 bottom left"></article>
|
||||
<article class="topoplot-c8 bottom right"></article>
|
||||
|
||||
<aside class="topoplot-grid">
|
||||
<div ng-repeat="pixel in $ctrl.grid track by $index" ng-style="$ctrl.getColor($index,pixel,$ctrl.grid)" ng-class="$ctrl.getClass($index)"></div>
|
||||
</aside>
|
||||
|
||||
</section>
|
||||
</section>
|
||||
@@ -1,44 +0,0 @@
|
||||
|
||||
(function () {
|
||||
|
||||
var BCITopo = {
|
||||
templateUrl: 'components/topo/topo.html',
|
||||
bindings: {
|
||||
eventName: '@'
|
||||
},
|
||||
controller: function ($timeout) {
|
||||
|
||||
var $ctrl = this;
|
||||
|
||||
var socket = io();
|
||||
|
||||
$ctrl.getClass = function(index){
|
||||
return 'topoplot-u' + index
|
||||
};
|
||||
|
||||
$ctrl.getColor = function(index,pixel,grid){
|
||||
var min = Math.min.apply(Math,grid);
|
||||
var max = Math.max.apply(Math,grid);
|
||||
var f = chroma.scale('Spectral').domain([min,max]);
|
||||
return {'background-color': f(pixel)}
|
||||
};
|
||||
|
||||
socket.on($ctrl.eventName, function (data) {
|
||||
$timeout(function () {
|
||||
$ctrl.grid = data.data;
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
$ctrl.$onDestroy = function () {
|
||||
socket.removeListener($ctrl.eventName);
|
||||
};
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
angular
|
||||
.module('bciDashboard')
|
||||
.component('bciTopo', BCITopo);
|
||||
|
||||
})();
|
||||
@@ -1,71 +0,0 @@
|
||||
<!doctype html>
|
||||
<html ng-app="bciDashboard">
|
||||
<head>
|
||||
<title>BCI Dashboard</title>
|
||||
|
||||
<link rel="stylesheet" href="node_modules/angular-chart.js/dist/angular-chart.css">
|
||||
<link rel="stylesheet" href="visualizer.css">
|
||||
<link rel="stylesheet" href="components/topo/topo.css">
|
||||
|
||||
<script src="node_modules/socket.io-client/socket.io.js"></script>
|
||||
<script src="node_modules/smoothie/smoothie.js"></script>
|
||||
|
||||
<script src="node_modules/angular/angular.js"></script>
|
||||
<script src="node_modules/chart.js/Chart.js"></script>
|
||||
<script src="node_modules/angular-chart.js/dist/angular-chart.js"></script>
|
||||
|
||||
<script src="visualizer.js"></script>
|
||||
|
||||
<script src="lib/chroma.min.js"></script>
|
||||
|
||||
<script src="components/frequency/frequency.js"></script>
|
||||
<script src="components/frequency-band/frequency-band.js"></script>
|
||||
<script src="components/time-series/time-series.js"></script>
|
||||
<script src="components/topo/topo.js"></script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<main>
|
||||
|
||||
<h1>BCI Dashboard</h1>
|
||||
|
||||
<nav ng-init="mode = 'timeSeries'">
|
||||
<button ng-click="mode = 'timeSeries'">Time Series</button>
|
||||
<button ng-click="mode = 'frequency'">Frequency</button>
|
||||
<button ng-click="mode = 'frequencyRadar'">Frequency Radar</button>
|
||||
<button ng-click="mode = 'frequencyBands'">Frequency Bands</button>
|
||||
<button ng-click="mode = 'topo'">Topo</button>
|
||||
</nav>
|
||||
|
||||
<section class="row" ng-if="mode == 'timeSeries'">
|
||||
<bci-time-series event-name="bci:time" class="block block-75"></bci-time-series>
|
||||
</section>
|
||||
|
||||
<section class="row" ng-if="mode == 'frequency'">
|
||||
<bci-frequency type="Line" event-name="bci:fft" class="block block-75"></bci-frequency>
|
||||
</section>
|
||||
|
||||
<section class="row" ng-if="mode == 'frequencyRadar'">
|
||||
<bci-frequency type="Radar" event-name="bci:fft" class="block block-75"></bci-frequency>
|
||||
</section>
|
||||
|
||||
<section class="row" ng-if="mode == 'frequencyBands'">
|
||||
<bci-frequency-band event-name="bci:fft" type="delta" color="1" class="block block-33"></bci-frequency-band>
|
||||
<bci-frequency-band event-name="bci:fft" type="theta" color="2" class="block block-33"></bci-frequency-band>
|
||||
</section>
|
||||
|
||||
<section class="row" ng-if="mode == 'frequencyBands'">
|
||||
<bci-frequency-band event-name="bci:fft" type="alpha" color="3" class="block block-33"></bci-frequency-band>
|
||||
<bci-frequency-band event-name="bci:fft" type="beta" color="4" class="block block-33"></bci-frequency-band>
|
||||
<bci-frequency-band event-name="bci:fft" type="gamma" color="5" class="block block-33"></bci-frequency-band>
|
||||
</section>
|
||||
|
||||
<section class="row" ng-if="mode == 'topo'">
|
||||
<bci-topo event-name="bci:topo" class="block block-50"></bci-topo>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
externo
-33
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
@@ -1,28 +0,0 @@
|
||||
|
||||
(function () {
|
||||
|
||||
angular
|
||||
.module('bciDashboard', ['chart.js'])
|
||||
.config(bciDashboardConfig);
|
||||
|
||||
function bciDashboardConfig (ChartJsProvider) {
|
||||
ChartJsProvider.setOptions({
|
||||
animation: false,
|
||||
responsive: true,
|
||||
datasetStrokeWidth: 1,
|
||||
pointDot: false,
|
||||
pointDotRadius: 1,
|
||||
pointDotStrokeWidth: 0,
|
||||
datasetFill: false,
|
||||
scaleOverride: true,
|
||||
scaleStartValue: -2,
|
||||
scaleStepWidth: 1,
|
||||
scaleSteps: 6,
|
||||
barShowStroke: false,
|
||||
barValueSpacing: 1,
|
||||
barStrokeWidth: 1,
|
||||
strokeColor: 'rgba(116,150,161,1)'
|
||||
});
|
||||
}
|
||||
|
||||
})();
|
||||
Arquivo binário não exibido.
|
Depois Largura: | Altura: | Tamanho: 120 KiB |
Arquivo binário não exibido.
|
Depois Largura: | Altura: | Tamanho: 123 KiB |
Arquivo binário não exibido.
|
Depois Largura: | Altura: | Tamanho: 76 KiB |
@@ -0,0 +1,3 @@
|
||||
export const environment = {
|
||||
production: false
|
||||
};
|
||||
@@ -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,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 { OpenbciDashboardPage } from './app.po';
|
||||
|
||||
describe('openbci-dashboard App', function() {
|
||||
let page: OpenbciDashboardPage;
|
||||
|
||||
beforeEach(() => {
|
||||
page = new OpenbciDashboardPage();
|
||||
});
|
||||
|
||||
it('should display message saying app works', () => {
|
||||
page.navigateTo();
|
||||
expect(page.getParagraphText()).toEqual('app works!');
|
||||
});
|
||||
});
|
||||
@@ -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,11 @@
|
||||
import { browser, element, by } from 'protractor/globals';
|
||||
|
||||
export class OpenbciDashboardPage {
|
||||
navigateTo() {
|
||||
return browser.get('/');
|
||||
}
|
||||
|
||||
getParagraphText() {
|
||||
return element(by.css('app-root h1')).getText();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"declaration": false,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"outDir": "../dist/out-tsc-e2e",
|
||||
"sourceMap": true,
|
||||
"target": "es5",
|
||||
"typeRoots": [
|
||||
"../node_modules/@types"
|
||||
]
|
||||
}
|
||||
}
|
||||
externo
+1
@@ -0,0 +1 @@
|
||||
/// <reference path="../typings/main.d.ts" />
|
||||
@@ -0,0 +1,35 @@
|
||||
// Karma configuration file, see link for more information
|
||||
// https://karma-runner.github.io/0.13/config/configuration-file.html
|
||||
|
||||
module.exports = function (config) {
|
||||
config.set({
|
||||
basePath: './',
|
||||
frameworks: ['jasmine', 'angular-cli'],
|
||||
plugins: [
|
||||
require('karma-jasmine'),
|
||||
require('karma-chrome-launcher'),
|
||||
require('karma-remap-istanbul'),
|
||||
require('angular-cli/plugins/karma')
|
||||
],
|
||||
files: [
|
||||
{ pattern: './src/test.ts', watched: false }
|
||||
],
|
||||
preprocessors: {
|
||||
'./src/test.ts': ['angular-cli']
|
||||
},
|
||||
remapIstanbulReporter: {
|
||||
reports: {
|
||||
html: 'coverage',
|
||||
lcovonly: './coverage/coverage.lcov'
|
||||
}
|
||||
},
|
||||
angularCliConfig: './angular-cli.json',
|
||||
reporters: ['progress', 'karma-remap-istanbul'],
|
||||
port: 9876,
|
||||
colors: true,
|
||||
logLevel: config.LOG_INFO,
|
||||
autoWatch: true,
|
||||
browsers: ['Chrome'],
|
||||
singleRun: false
|
||||
});
|
||||
};
|
||||
+46
-26
@@ -1,42 +1,62 @@
|
||||
{
|
||||
"name": "openbci-dashboard",
|
||||
"private": false,
|
||||
"version": "0.0.1",
|
||||
"description": "A fullstack javascript app for capturing and visualizing OpenBCI EEG data",
|
||||
"main": "visualizer.js",
|
||||
"version": "0.0.0",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"openbci",
|
||||
"eeg",
|
||||
"neurojs",
|
||||
"dashboard"
|
||||
],
|
||||
"author": "Alex Castillo",
|
||||
"angular-cli": {},
|
||||
"scripts": {
|
||||
"start": "node visualizer",
|
||||
"simulate": "node visualizer simulate"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:NeuroJS/openbci-dashboard.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/NeuroJS/openbci-dashboard/issues"
|
||||
"start": "ng serve",
|
||||
"lint": "tslint \"src/**/*.ts\"",
|
||||
"test": "ng test",
|
||||
"pree2e": "webdriver-manager update",
|
||||
"e2e": "protractor"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"angular": "^1.5.5",
|
||||
"angular-chart.js": "^0.10.2",
|
||||
"chart.js": "^1.1.1",
|
||||
"@angular/common": "2.0.0-rc.5",
|
||||
"@angular/compiler": "2.0.0-rc.5",
|
||||
"@angular/core": "2.0.0-rc.5",
|
||||
"@angular/forms": "0.3.0",
|
||||
"@angular/http": "2.0.0-rc.5",
|
||||
"@angular/platform-browser": "2.0.0-rc.5",
|
||||
"@angular/platform-browser-dynamic": "2.0.0-rc.5",
|
||||
"@angular/router": "3.0.0-rc.1",
|
||||
"brainbrowser": "^2.3.0",
|
||||
"chart.js": "^2.1.4",
|
||||
"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.1.0",
|
||||
"nodemon": "^1.9.1",
|
||||
"openbci-sdk": "^0.3.4",
|
||||
"plotly.js": "^1.10.2",
|
||||
"reflect-metadata": "^0.1.3",
|
||||
"smoothie": "^1.27.0",
|
||||
"socket.io": "^1.4.5",
|
||||
"socket.io-client": "^1.4.5",
|
||||
"socket.io": "^1.4.6",
|
||||
"socket.io-client": "^1.4.6",
|
||||
"systemjs": "0.19.27",
|
||||
"topogrid": "^1.0.6",
|
||||
"yargs": "^4.3.2"
|
||||
"yargs": "^4.3.2",
|
||||
"core-js": "^2.4.1",
|
||||
"zone.js": "^0.6.17"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jasmine": "^2.2.30",
|
||||
"@types/smoothie": "^1.25.27",
|
||||
"@types/socket.io-client": "^1.4.26",
|
||||
"angular-cli": "1.0.0-beta.11-webpack.8",
|
||||
"codelyzer": "~0.0.26",
|
||||
"jasmine-core": "2.4.1",
|
||||
"jasmine-spec-reporter": "2.5.0",
|
||||
"karma": "0.13.22",
|
||||
"karma-chrome-launcher": "0.2.3",
|
||||
"karma-jasmine": "0.3.8",
|
||||
"karma-remap-istanbul": "^0.2.1",
|
||||
"protractor": "4.0.3",
|
||||
"ts-node": "1.2.1",
|
||||
"tslint": "3.13.0",
|
||||
"typescript": "^2.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
// Protractor configuration file, see link for more information
|
||||
// https://github.com/angular/protractor/blob/master/docs/referenceConf.js
|
||||
|
||||
/*global jasmine */
|
||||
var SpecReporter = require('jasmine-spec-reporter');
|
||||
|
||||
exports.config = {
|
||||
allScriptsTimeout: 11000,
|
||||
specs: [
|
||||
'./e2e/**/*.e2e-spec.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());
|
||||
}
|
||||
};
|
||||
@@ -1,21 +1,28 @@
|
||||
|
||||
@import url(https://fonts.googleapis.com/css?family=Roboto:400,700,300);
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
font-weight: 300;
|
||||
color: #ffffff;
|
||||
background-color: #222222;
|
||||
:host {
|
||||
display: block;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.content {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 20px;
|
||||
padding: 20px;
|
||||
font-weight: 300;
|
||||
margin: 0;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.capitalize {
|
||||
@@ -32,57 +39,6 @@ h1 {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
nav {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
|
||||
button {
|
||||
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;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
nav button:nth-child(1) {
|
||||
background-color: rgba(112,185,252,1);
|
||||
}
|
||||
|
||||
nav button:nth-child(2) {
|
||||
background-color: rgba(138,219,229,1);
|
||||
}
|
||||
|
||||
nav button:nth-child(3) {
|
||||
background-color: rgba(162,86,178,1);
|
||||
}
|
||||
|
||||
nav button:nth-child(4) {
|
||||
background-color: rgba(144,132,246,1);
|
||||
}
|
||||
|
||||
nav button:nth-child(5) {
|
||||
background-color: rgba(232,223,133,1);
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
nav button:nth-child(6) {
|
||||
background-color: rgba(148,159,177,1);
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
}
|
||||
@@ -131,64 +87,57 @@ nav button:nth-child(6) {
|
||||
min-height: 580px;
|
||||
}
|
||||
|
||||
.time-series canvas {
|
||||
margin: 40px 80px 40px 40px;
|
||||
|
||||
nav {
|
||||
margin-left: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.time-series-channels,
|
||||
.time-series-amplitudes {
|
||||
position: absolute;
|
||||
top: 60px;
|
||||
height: 450px;
|
||||
color: rgba(102,102,102,1);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
.time-series-channels {
|
||||
width: 40px;
|
||||
left: 20px;
|
||||
text-align: left;
|
||||
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;
|
||||
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%;
|
||||
nav a:nth-child(6) {
|
||||
background-color: rgba(148,159,177,1);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<main>
|
||||
<bci-filters></bci-filters>
|
||||
<section class="content">
|
||||
<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]="['/motion']">Motion</a>
|
||||
<a [routerLink]="['/topo']">Topo</a>
|
||||
</nav>
|
||||
<router-outlet></router-outlet>
|
||||
</section>
|
||||
</main>
|
||||
@@ -0,0 +1,18 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'bci-dashboard',
|
||||
templateUrl: 'dashboard.component.html',
|
||||
styleUrls: ['dashboard.component.css'],
|
||||
})
|
||||
|
||||
export class DashboardComponent implements OnInit {
|
||||
title = 'BCI Dashboard';
|
||||
|
||||
constructor () {
|
||||
}
|
||||
|
||||
ngOnInit () {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
|
||||
import { routing, appRoutingProviders } from './dashboard.routing';
|
||||
|
||||
import { DashboardComponent } from './dashboard.component';
|
||||
import { FiltersComponent } from './filters';
|
||||
import { TimeSeriesComponent } from './time-series';
|
||||
import { FrequencyComponent } from './frequency';
|
||||
import { FrequencyBandsComponent } from './frequency-bands';
|
||||
import { TopoComponent } from './topo';
|
||||
import { MotionComponent } from './motion';
|
||||
|
||||
import { CHART_DIRECTIVES } from './shared/ng2-charts';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
BrowserModule,
|
||||
routing
|
||||
],
|
||||
declarations: [
|
||||
DashboardComponent,
|
||||
FiltersComponent,
|
||||
TimeSeriesComponent,
|
||||
FrequencyComponent,
|
||||
FrequencyBandsComponent,
|
||||
TopoComponent,
|
||||
MotionComponent,
|
||||
CHART_DIRECTIVES
|
||||
],
|
||||
providers: [
|
||||
appRoutingProviders
|
||||
],
|
||||
bootstrap: [
|
||||
DashboardComponent
|
||||
]
|
||||
})
|
||||
export class DashboardModule { }
|
||||
@@ -0,0 +1,23 @@
|
||||
import { Routes, RouterModule } from '@angular/router';
|
||||
|
||||
import { TimeSeriesComponent } from './time-series';
|
||||
import { FrequencyComponent } from './frequency';
|
||||
import { FrequencyBandsComponent } from './frequency-bands';
|
||||
import { TopoComponent } from './topo';
|
||||
import { MotionComponent } from './motion';
|
||||
|
||||
const dashboardRoutes: Routes = [
|
||||
{ path: '', redirectTo: '/time-series', pathMatch: 'full' },
|
||||
{ path: 'time-series', component: TimeSeriesComponent },
|
||||
{ path: 'frequency/line', component: FrequencyComponent },
|
||||
{ path: 'frequency/radar', component: FrequencyComponent },
|
||||
{ path: 'frequency/bands', component: FrequencyBandsComponent },
|
||||
{ path: 'motion', component: MotionComponent },
|
||||
{ path: 'topo', component: TopoComponent }
|
||||
];
|
||||
|
||||
export const appRoutingProviders: any[] = [
|
||||
|
||||
];
|
||||
|
||||
export const routing = RouterModule.forRoot(dashboardRoutes);
|
||||
@@ -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,66 @@
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.filters {
|
||||
width: 120px;
|
||||
height: 100vh;
|
||||
padding: 14px;
|
||||
background-color: #333;
|
||||
}
|
||||
|
||||
.filters h3 {
|
||||
font-weight: 500;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.filters article {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.filters nav {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.filters label {
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.filters .select {
|
||||
position: relative;
|
||||
border: 2px solid rgba(255,255,255, 0.6);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.filters .select:after {
|
||||
content: "▼";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
font-size: 6px;
|
||||
line-height: 27px;
|
||||
padding: 0 8px;
|
||||
color: rgba(255,255,255,0.2);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.filters select {
|
||||
width: 100%;
|
||||
-webkit-appearance: none;
|
||||
color: rgba(255,255,255,0.4);
|
||||
font-weight: 300;
|
||||
letter-spacing: 1px;
|
||||
background: transparent;
|
||||
padding: 6px;
|
||||
outline: none;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.filters .disabled {
|
||||
opacity: 0.2;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<aside class="filters">
|
||||
<h3>Filters</h3>
|
||||
<nav>
|
||||
<article *ngFor="let filter of filters"
|
||||
[ngClass]="{ disabled: !filter.enabled }">
|
||||
<label>{{ filter.label }}</label>
|
||||
<div class="select">
|
||||
<select
|
||||
[multiple]="filter.type === 'multiple'"
|
||||
[disabled]="!filter.enabled"
|
||||
(change)="applyFilter($event.target.value)">
|
||||
<option *ngFor="let option of filter.options"
|
||||
[value]="option.id">
|
||||
{{ option.label }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</article>
|
||||
</nav>
|
||||
</aside>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import * as io from 'socket.io-client';
|
||||
import { Constants } from '../shared/constants';
|
||||
|
||||
@Component({
|
||||
selector: 'bci-filters',
|
||||
templateUrl: 'filters.component.html',
|
||||
styleUrls: ['filters.component.css'],
|
||||
providers: [Constants]
|
||||
})
|
||||
|
||||
export class FiltersComponent {
|
||||
|
||||
socket: any;
|
||||
constructor(private constants: Constants) {
|
||||
this.socket = io(this.constants.socket.url);
|
||||
}
|
||||
|
||||
private filters: Array<any> = this.constants.filters;
|
||||
|
||||
applyFilter (filter) {
|
||||
this.socket.emit(this.constants.socket.events.filter, filter)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { FiltersComponent } from './filters.component';
|
||||
@@ -0,0 +1,36 @@
|
||||
|
||||
.chart {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
:host {
|
||||
display: inline-block;
|
||||
margin: 0 0 20px 20px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 0 5px rgba(0,0,0,0.3);
|
||||
background-color: #333333;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 47%;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
h2 {
|
||||
position: absolute;
|
||||
margin: 0;
|
||||
top: 10px;
|
||||
right: 20px;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.capitalize {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.frequency-band {
|
||||
height: 100%;
|
||||
padding-top: 40px;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<section class="frequency-band" [ngClass]="{ 'loading': !data }">
|
||||
<h2 class="capitalize">{{ band }} {{ type }}</h2>
|
||||
<base-chart class="chart"
|
||||
[datasets]="data"
|
||||
[labels]="channels"
|
||||
[options]="options"
|
||||
[colors]="colors"
|
||||
[legend]="false"
|
||||
[series]="channels"
|
||||
[chartType]="type">
|
||||
</base-chart>
|
||||
</section>
|
||||
@@ -0,0 +1,46 @@
|
||||
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({
|
||||
selector: 'bci-frequency-band',
|
||||
templateUrl: 'frequency-band.component.html',
|
||||
styleUrls: ['frequency-band.component.css'],
|
||||
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> = [{ data: [], label: [] }];
|
||||
private colors:Array<any>;
|
||||
private channels:Array<string> = this.chartService.getChannels();
|
||||
private options:any = this.chartService.getChartJSBarDefaults();
|
||||
|
||||
ngOnInit() {
|
||||
this.colors = this.chartService.getColorByIndex(this.color);
|
||||
this.socket.on(this.constants.socket.events.fft, (data) => {
|
||||
this.data = [];
|
||||
data[this.band || 'data'].forEach((dataset, index) => {
|
||||
this.data.push({
|
||||
data: dataset
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy () {
|
||||
this.socket.removeListener(this.constants.socket.events.fft);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { FrequencyBandComponent } from './frequency-band.component';
|
||||
@@ -0,0 +1,11 @@
|
||||
<div>
|
||||
<!--<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>-->
|
||||
</div>
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'bci-frequency-bands',
|
||||
templateUrl: 'frequency-bands.component.html',
|
||||
styleUrls: ['frequency-bands.component.css']
|
||||
})
|
||||
|
||||
export class FrequencyBandsComponent implements OnInit {
|
||||
|
||||
constructor() {}
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { FrequencyBandsComponent } from './frequency-bands.component';
|
||||
@@ -0,0 +1,30 @@
|
||||
|
||||
.chart {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
:host {
|
||||
display: block;
|
||||
margin: 20px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 0 5px rgba(0,0,0,0.3);
|
||||
background-color: #333333;
|
||||
position: relative;
|
||||
height: 600px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
h2 {
|
||||
position: absolute;
|
||||
margin: 0;
|
||||
top: 10px;
|
||||
right: 20px;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.frequency {
|
||||
height: 100%;
|
||||
padding-top: 40px;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<section class="frequency" [ngClass]="{ 'loading': !data }">
|
||||
<h2>Frequency {{ type }}</h2>
|
||||
<base-chart class="chart"
|
||||
[datasets]="data"
|
||||
[labels]="labels"
|
||||
[options]="options"
|
||||
[colors]="colors"
|
||||
[series]="channels"
|
||||
[chartType]="type">
|
||||
</base-chart>
|
||||
</section>
|
||||
@@ -0,0 +1,63 @@
|
||||
import { Component, ElementRef, OnInit, OnDestroy, Input } from '@angular/core';
|
||||
import { Router, ActivatedRoute } from '@angular/router';
|
||||
import * as io from 'socket.io-client';
|
||||
import { ChartService } from '../shared';
|
||||
import { Constants } from '../shared/constants';
|
||||
|
||||
@Component({
|
||||
selector: 'bci-frequency',
|
||||
templateUrl: 'frequency.component.html',
|
||||
styleUrls: ['frequency.component.css'],
|
||||
providers: [ChartService, Constants]
|
||||
})
|
||||
|
||||
export class FrequencyComponent implements OnInit {
|
||||
|
||||
socket: any;
|
||||
constructor(private chartService: ChartService,
|
||||
private route: ActivatedRoute,
|
||||
private constants: Constants) {
|
||||
this.socket = io(constants.socket.url);
|
||||
this.type = this.route.snapshot.params['type'] || 'line';
|
||||
|
||||
this.setOptions(this.type);
|
||||
}
|
||||
|
||||
@Input() type:string;
|
||||
|
||||
private data:Array<any> = [{ data: [], label: [] }];
|
||||
private labels:Array<any> = [];
|
||||
private colors:Array<any> = this.chartService.getColors();
|
||||
private channels:Array<string> = this.chartService.getChannels();
|
||||
private options:any;
|
||||
|
||||
setOptions (type) {
|
||||
if (type === 'line') {
|
||||
this.options = this.chartService.getChartJSLineDefaults();
|
||||
}
|
||||
if (type === 'radar') {
|
||||
this.options = this.chartService.getChartJSRadarDefaults();
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.socket.on(this.constants.socket.events.fft, (data) => {
|
||||
this.data = [];
|
||||
data.data.forEach((dataset, index) => {
|
||||
this.data.push({
|
||||
data: dataset,
|
||||
label: this.channels[index],
|
||||
borderWidth: 1,
|
||||
pointRadius: 0,
|
||||
fill: false
|
||||
});
|
||||
});
|
||||
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 * from './app.component';
|
||||
export * from './app.module';
|
||||
@@ -0,0 +1 @@
|
||||
export { MotionComponent } from './motion.component';
|
||||
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
Arquivo binário não exibido.
@@ -0,0 +1,25 @@
|
||||
|
||||
: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: 768px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
h2 {
|
||||
position: absolute;
|
||||
margin: 0;
|
||||
top: 10px;
|
||||
right: 20px;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
#brainbrowser {
|
||||
width: 100%;
|
||||
height: 500px;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<section class="motion" [ngClass]="{ 'loading': !model }">
|
||||
<h2>Motion</h2>
|
||||
<article id="brainbrowser"></article>
|
||||
</section>
|
||||
@@ -0,0 +1,52 @@
|
||||
import { Component, OnInit, ElementRef } from '@angular/core';
|
||||
import * as io from 'socket.io-client';
|
||||
import { Constants } from '../shared/constants';
|
||||
|
||||
declare var BrainBrowser;
|
||||
|
||||
@Component({
|
||||
selector: 'bci-motion',
|
||||
templateUrl: 'motion.component.html',
|
||||
styleUrls: ['motion.component.css'],
|
||||
providers: [Constants]
|
||||
})
|
||||
|
||||
export class MotionComponent implements OnInit {
|
||||
|
||||
socket: any;
|
||||
viewer: any;
|
||||
model: any;
|
||||
constructor(private view: ElementRef, private constants: Constants) {
|
||||
this.socket = io(constants.socket.url);
|
||||
}
|
||||
|
||||
private rotation = [];
|
||||
|
||||
ngOnInit() {
|
||||
BrainBrowser.config.set('worker_dir', 'vendor/brainbrowser/build/brainbrowser-2.3.0/workers');
|
||||
|
||||
this.socket.on(this.constants.socket.events.motion, (data) => {
|
||||
this.rotation = data.data;
|
||||
|
||||
if (this.model) {
|
||||
this.viewer.resetView();
|
||||
this.viewer.model.rotation.set(...this.rotation);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngAfterViewInit () {
|
||||
this.viewer = BrainBrowser.SurfaceViewer.start('brainbrowser', (viewer) => {
|
||||
this.viewer = viewer;
|
||||
this.viewer.render();
|
||||
this.viewer.setWireframe(true);
|
||||
this.viewer.loadModelFromURL('app/motion/models/brain-surface.obj');
|
||||
this.viewer.setClearColor(0x333333);
|
||||
|
||||
this.viewer.addEventListener('displaymodel', (data) => {
|
||||
this.model = data.model;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
@Injectable()
|
||||
export class ChartService {
|
||||
|
||||
constructor() {}
|
||||
|
||||
getPlotlyContourLayout (overrides: any = {}): any {
|
||||
return Object.assign({
|
||||
bargap: 0,
|
||||
autosize: true,
|
||||
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
|
||||
}
|
||||
}, overrides);
|
||||
}
|
||||
|
||||
getPlotlyContourData (overrides: any = {}): any {
|
||||
return Object.assign({
|
||||
z: [],
|
||||
x: [],
|
||||
y: [],
|
||||
type: 'contour',
|
||||
colorscale: 'Jet',
|
||||
showscale: false
|
||||
}, overrides);
|
||||
}
|
||||
|
||||
getChartJSGlobalDefaults (overrides: any = {}): any {
|
||||
return Object.assign({
|
||||
responsive: true,
|
||||
animation: false,
|
||||
legend: {
|
||||
display: true,
|
||||
position: 'bottom'
|
||||
},
|
||||
}, overrides);
|
||||
}
|
||||
|
||||
getChartJSLineDefaults (overrides: any = {}): any {
|
||||
return Object.assign({
|
||||
scales: {
|
||||
xAxes: [{
|
||||
gridLines: {
|
||||
display: true
|
||||
},
|
||||
ticks: {
|
||||
max: 4,
|
||||
min: -2,
|
||||
stepSize: 0.5
|
||||
}
|
||||
}],
|
||||
yAxes: [{
|
||||
gridLines: {
|
||||
display: true
|
||||
},
|
||||
ticks: {
|
||||
max: 4,
|
||||
min: -2,
|
||||
stepSize: 0.5
|
||||
}
|
||||
}]
|
||||
}
|
||||
}, this.getChartJSGlobalDefaults());
|
||||
}
|
||||
|
||||
getChartJSBarDefaults (overrides: any = {}): any {
|
||||
return Object.assign({
|
||||
scales: {
|
||||
xAxes: [{
|
||||
gridLines: {
|
||||
display: false
|
||||
},
|
||||
ticks: {
|
||||
max: 4,
|
||||
min: 0,
|
||||
stepSize: 0.5
|
||||
}
|
||||
}],
|
||||
yAxes: [{
|
||||
gridLines: {
|
||||
display: false
|
||||
},
|
||||
ticks: {
|
||||
max: 4,
|
||||
min: 0,
|
||||
stepSize: 0.5
|
||||
}
|
||||
}]
|
||||
}
|
||||
}, this.getChartJSGlobalDefaults({
|
||||
legend: {
|
||||
display: false
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
getChartJSRadarDefaults (overrides: any = {}): any {
|
||||
return Object.assign({
|
||||
scale: {
|
||||
lineArc: false,
|
||||
angleLines: {
|
||||
display: true
|
||||
},
|
||||
ticks: {
|
||||
max: 4,
|
||||
min: -2,
|
||||
stepSize: 0.5,
|
||||
showLabelBackdrop: false
|
||||
}
|
||||
}
|
||||
}, this.getChartJSGlobalDefaults());
|
||||
}
|
||||
|
||||
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 [
|
||||
{ borderColor: 'rgba(112,185,252,1)', backgroundColor: 'rgba(112,185,252,1)' },
|
||||
{ borderColor: 'rgba(116,150,161,1)', backgroundColor: 'rgba(116,150,161,1)' },
|
||||
{ borderColor: 'rgba(162,86,178,1)', backgroundColor: 'rgba(162,86,178,1)' },
|
||||
{ borderColor: 'rgba(144,132,246,1)', backgroundColor: 'rgba(144,132,246,1)' },
|
||||
{ borderColor: 'rgba(138,219,229,1)', backgroundColor: 'rgba(138,219,229,1)' },
|
||||
{ borderColor: 'rgba(232,223,133,1)', backgroundColor: 'rgba(232,223,133,1)' },
|
||||
{ borderColor: 'rgba(148,159,177,1)', backgroundColor: 'rgba(148,159,177,1)' },
|
||||
{ borderColor: 'rgba(77,83,96,1)', backgroundColor: 'rgba(77,83,96,1)' }
|
||||
];
|
||||
}
|
||||
|
||||
getColorByIndex (index:number): Array<any> {
|
||||
return this.getColors().filter((c, i) => index === i);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
@Injectable()
|
||||
export class Constants {
|
||||
|
||||
socket: any = {
|
||||
url: 'http://localhost:8080',
|
||||
events: {
|
||||
fft: 'bci:fft',
|
||||
time: 'bci:time',
|
||||
topo: 'bci:topo',
|
||||
filter: 'bci:filter',
|
||||
motion: 'bci:motion'
|
||||
}
|
||||
};
|
||||
|
||||
filters: Array<any> = [
|
||||
{
|
||||
id: 'NOTCH',
|
||||
label: 'Notch',
|
||||
type: 'single',
|
||||
enabled: true,
|
||||
options: [
|
||||
{
|
||||
id: 'NOTCH:60',
|
||||
label: '60 Hz'
|
||||
},
|
||||
{
|
||||
id: 'NOTCH:50',
|
||||
label: '50 Hz'
|
||||
},
|
||||
{
|
||||
id: 'NOTCH:NONE',
|
||||
label: 'None'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'BANDPASS',
|
||||
label: 'Band Pass',
|
||||
type: 'single',
|
||||
enabled: false,
|
||||
options: [
|
||||
{
|
||||
id: 'BANDPASS:1-50',
|
||||
label: '1-50 Hz'
|
||||
},
|
||||
{
|
||||
id: 'BANDPASS:7-13',
|
||||
label: '7-13 Hz'
|
||||
},
|
||||
{
|
||||
id: 'BANDPASS:15-50',
|
||||
label: '15-50 Hz'
|
||||
},
|
||||
{
|
||||
id: 'BANDPASS:5-50',
|
||||
label: '5-50 Hz'
|
||||
},
|
||||
{
|
||||
id: 'BANDPASS:NONE',
|
||||
label: 'None'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'VERTSCALE',
|
||||
label: 'Vert Scale',
|
||||
type: 'single',
|
||||
enabled: false,
|
||||
options: [
|
||||
{
|
||||
id: 'VERTSCALE:50',
|
||||
label: '50uV'
|
||||
},
|
||||
{
|
||||
id: 'VERTSCALE:100',
|
||||
label: '100uV'
|
||||
},
|
||||
{
|
||||
id: 'VERTSCALE:200',
|
||||
label: '200uV'
|
||||
},
|
||||
{
|
||||
id: 'VERTSCALE:400',
|
||||
label: '400uV'
|
||||
},
|
||||
{
|
||||
id: 'VERTSCALE:1000',
|
||||
label: '1000uV'
|
||||
},
|
||||
{
|
||||
id: 'VERTSCALE:10000',
|
||||
label: '10,000uV'
|
||||
},
|
||||
{
|
||||
id: 'VERTSCALE:NONE',
|
||||
label: 'None'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'VERTALGO',
|
||||
label: 'Vert Algo',
|
||||
type: 'single',
|
||||
enabled: false,
|
||||
options: [
|
||||
{
|
||||
id: 'VERTALGO:50',
|
||||
label: 'Log'
|
||||
},
|
||||
{
|
||||
id: 'VERTALGO:Linear',
|
||||
label: 'Linear'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'SMOOTH',
|
||||
label: 'Smooth',
|
||||
type: 'single',
|
||||
enabled: false,
|
||||
options: [
|
||||
{
|
||||
id: 'SMOOTH:0-75',
|
||||
label: '0 75'
|
||||
},
|
||||
{
|
||||
id: 'SMOOTH:0-9',
|
||||
label: '0 9'
|
||||
},
|
||||
{
|
||||
id: 'SMOOTH:0-95',
|
||||
label: '0 95'
|
||||
},
|
||||
{
|
||||
id: 'SMOOTH:0-98',
|
||||
label: '0 98'
|
||||
},
|
||||
{
|
||||
id: 'SMOOTH:0-0',
|
||||
label: '0 0'
|
||||
},
|
||||
{
|
||||
id: 'SMOOTH:0-5',
|
||||
label: '0 5'
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'POLARITY',
|
||||
label: 'Polarity',
|
||||
type: 'single',
|
||||
enabled: false,
|
||||
options: [
|
||||
{
|
||||
id: 'POLARITY:Yes',
|
||||
label: 'Yes'
|
||||
},
|
||||
{
|
||||
id: 'POLARITY:NO',
|
||||
label: 'No'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'MAXFREQUENCY',
|
||||
label: 'Max Frequency',
|
||||
type: 'single',
|
||||
enabled: false,
|
||||
options: [
|
||||
{
|
||||
id: 'MAXFREQUENCY:60',
|
||||
label: '60 Hz'
|
||||
},
|
||||
{
|
||||
id: 'MAXFREQUENCY:120',
|
||||
label: '120 Hz'
|
||||
},
|
||||
{
|
||||
id: 'MAXFREQUENCY:20',
|
||||
label: '20 Hz'
|
||||
},
|
||||
{
|
||||
id: 'MAXFREQUENCY:40',
|
||||
label: '40 Hz'
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './chart.service';
|
||||
export * from './ng2-charts';
|
||||
@@ -0,0 +1,287 @@
|
||||
import {
|
||||
Component, OnDestroy, OnInit, OnChanges, EventEmitter, ElementRef, Input,
|
||||
Output
|
||||
} from '@angular/core';
|
||||
|
||||
declare var Chart:any;
|
||||
|
||||
@Component({
|
||||
selector: 'base-chart',
|
||||
template: `<canvas style="width: 100%; height: 100%;"></canvas>`,
|
||||
})
|
||||
export class BaseChartComponent implements OnDestroy, OnChanges, OnInit {
|
||||
public static defaultColors:Array<number[]> = [
|
||||
[255, 99, 132],
|
||||
[54, 162, 235],
|
||||
[255, 206, 86],
|
||||
[231, 233, 237],
|
||||
[75, 192, 192],
|
||||
[151, 187, 205],
|
||||
[220, 220, 220],
|
||||
[247, 70, 74],
|
||||
[70, 191, 189],
|
||||
[253, 180, 92],
|
||||
[148, 159, 177],
|
||||
[77, 83, 96]
|
||||
];
|
||||
|
||||
@Input() public data:number[] | Array<number[]>;
|
||||
@Input() public datasets:any[];
|
||||
@Input() public labels:Array<any> = [];
|
||||
@Input() public options:any = {responsive: true};
|
||||
@Input() public chartType:string;
|
||||
@Input() public colors:Array<any>;
|
||||
@Input() public legend:boolean;
|
||||
@Input() public series:any;
|
||||
|
||||
@Output() public chartClick:EventEmitter<any> = new EventEmitter();
|
||||
@Output() public chartHover:EventEmitter<any> = new EventEmitter();
|
||||
|
||||
private ctx:any;
|
||||
private cvs:any;
|
||||
private parent:any;
|
||||
private chart:any;
|
||||
private initFlag:boolean = false;
|
||||
|
||||
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.initFlag = true;
|
||||
if (this.data || this.datasets) {
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
public ngOnChanges():any {
|
||||
if (this.initFlag) {
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
public ngOnDestroy():any {
|
||||
if (this.chart) {
|
||||
this.chart.destroy();
|
||||
this.chart = void 0;
|
||||
}
|
||||
}
|
||||
|
||||
public getChartBuilder(ctx:any/*, data:Array<any>, options:any*/):any {
|
||||
let datasets:any = void 0;
|
||||
|
||||
// in case if datasets is not provided, but data is present
|
||||
if (!this.datasets || !this.datasets.length && (this.data && this.data.length)) {
|
||||
if (Array.isArray(this.data[0])) {
|
||||
datasets = (this.data as Array<number[]>).map((data:number[], index:number) => {
|
||||
return {data, label: this.labels[index] || `Label ${index}`};
|
||||
});
|
||||
} else {
|
||||
datasets = [{data: this.data, label: `Label 0`}];
|
||||
}
|
||||
}
|
||||
|
||||
if (this.datasets && this.datasets.length ||
|
||||
(datasets && datasets.length)) {
|
||||
datasets = (this.datasets || datasets)
|
||||
.map((elm:number, index:number) => {
|
||||
let newElm:any = Object.assign({}, elm);
|
||||
if (this.colors && this.colors.length) {
|
||||
Object.assign(newElm, this.colors[index]);
|
||||
} else {
|
||||
Object.assign(newElm, getColors(this.chartType, index, newElm.data.length));
|
||||
}
|
||||
return newElm;
|
||||
});
|
||||
}
|
||||
|
||||
if (!datasets) {
|
||||
throw new Error(`ng-charts configuration error,
|
||||
data or datasets field are required to render char ${this.chartType}`);
|
||||
}
|
||||
|
||||
let options:any = Object.assign({}, this.options);
|
||||
// hock for onHover and onClick events
|
||||
options.hover = options.hover || {};
|
||||
if (!options.hover.onHover) {
|
||||
options.hover.onHover = (active:Array<any>) => {
|
||||
if (active && !active.length) {
|
||||
return;
|
||||
}
|
||||
this.chartHover.emit({active});
|
||||
};
|
||||
}
|
||||
|
||||
if (!options.onClick) {
|
||||
options.onClick = (event:any, active:Array<any>) => {
|
||||
this.chartClick.emit({event, active});
|
||||
};
|
||||
}
|
||||
|
||||
let opts = {
|
||||
type: this.chartType,
|
||||
data: {
|
||||
labels: this.labels,
|
||||
datasets: datasets
|
||||
},
|
||||
options: options
|
||||
};
|
||||
|
||||
if (typeof Chart === 'undefined') {
|
||||
throw new Error('ng2-charts configuration issue: Embedding Chart.js lib is mandatory');
|
||||
}
|
||||
|
||||
return new Chart(ctx, opts);
|
||||
}
|
||||
|
||||
private refresh():any {
|
||||
if (this.options && this.options.responsive && this.parent.clientHeight === 0) {
|
||||
return setTimeout(() => this.refresh(), 50);
|
||||
}
|
||||
|
||||
// todo: remove this line, it is producing flickering
|
||||
this.ngOnDestroy();
|
||||
this.chart = this.getChartBuilder(this.ctx/*, data, this.options*/);
|
||||
}
|
||||
}
|
||||
|
||||
// private helper functions
|
||||
export interface Color {
|
||||
backgroundColor?:string | string[];
|
||||
borderWidth?:number | number[];
|
||||
borderColor?:string | string[];
|
||||
borderCapStyle?:string;
|
||||
borderDash?:number[];
|
||||
borderDashOffset?:number;
|
||||
borderJoinStyle?:string;
|
||||
|
||||
pointBorderColor?:string | string[];
|
||||
pointBackgroundColor?:string | string[];
|
||||
pointBorderWidth?:number | number[];
|
||||
|
||||
pointRadius?:number | number[];
|
||||
pointHoverRadius?:number | number[];
|
||||
pointHitRadius?:number | number[];
|
||||
|
||||
pointHoverBackgroundColor?:string | string[];
|
||||
pointHoverBorderColor?:string | string[];
|
||||
pointHoverBorderWidth?:number | number[];
|
||||
pointStyle?:string | string[];
|
||||
|
||||
hoverBackgroundColor?:string | string[];
|
||||
hoverBorderColor?:string | string[];
|
||||
hoverBorderWidth?:number;
|
||||
}
|
||||
|
||||
// pie | doughnut
|
||||
export interface Colors extends Color {
|
||||
data?:number[];
|
||||
label?:string;
|
||||
}
|
||||
|
||||
function rgba(colour:Array<number>, alpha:number):string {
|
||||
return 'rgba(' + colour.concat(alpha).join(',') + ')';
|
||||
}
|
||||
|
||||
function getRandomInt(min:number, max:number):number {
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
}
|
||||
|
||||
function formatLineColor(colors:Array<number>):Color {
|
||||
return {
|
||||
backgroundColor: rgba(colors, 0.4),
|
||||
borderColor: rgba(colors, 1),
|
||||
pointBackgroundColor: rgba(colors, 1),
|
||||
pointBorderColor: '#fff',
|
||||
pointHoverBackgroundColor: '#fff',
|
||||
pointHoverBorderColor: rgba(colors, 0.8)
|
||||
};
|
||||
}
|
||||
|
||||
function formatBarColor(colors:Array<number>):Color {
|
||||
return {
|
||||
backgroundColor: rgba(colors, 0.6),
|
||||
borderColor: rgba(colors, 1),
|
||||
hoverBackgroundColor: rgba(colors, 0.8),
|
||||
hoverBorderColor: rgba(colors, 1)
|
||||
};
|
||||
}
|
||||
|
||||
function formatPieColors(colors:Array<number[]>):Colors {
|
||||
return {
|
||||
backgroundColor: colors.map((color:number[]) => rgba(color, 0.6)),
|
||||
borderColor: colors.map(() => '#fff'),
|
||||
pointBackgroundColor: colors.map((color:number[]) => rgba(color, 1)),
|
||||
pointBorderColor: colors.map(() => '#fff'),
|
||||
pointHoverBackgroundColor: colors.map((color:number[]) => rgba(color, 1)),
|
||||
pointHoverBorderColor: colors.map((color:number[]) => rgba(color, 1))
|
||||
};
|
||||
}
|
||||
|
||||
function formatPolarAreaColors(colors:Array<number[]>):Color {
|
||||
return {
|
||||
backgroundColor: colors.map((color:number[]) => rgba(color, 0.6)),
|
||||
borderColor: colors.map((color:number[]) => rgba(color, 1)),
|
||||
hoverBackgroundColor: colors.map((color:number[]) => rgba(color, 0.8)),
|
||||
hoverBorderColor: colors.map((color:number[]) => rgba(color, 1))
|
||||
};
|
||||
}
|
||||
|
||||
function getRandomColor():number[] {
|
||||
return [getRandomInt(0, 255), getRandomInt(0, 255), getRandomInt(0, 255)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate colors for line|bar charts
|
||||
* @param index
|
||||
* @returns {number[]|Color}
|
||||
*/
|
||||
function generateColor(index:number):number[] {
|
||||
return BaseChartComponent.defaultColors[index] || getRandomColor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate colors for pie|doughnut charts
|
||||
* @param count
|
||||
* @returns {Colors}
|
||||
*/
|
||||
function generateColors(count:number):Array<number[]> {
|
||||
let colorsArr:Array<number[]> = new Array(count);
|
||||
for (let i = 0; i < count; i++) {
|
||||
colorsArr[i] = BaseChartComponent.defaultColors[i] || getRandomColor();
|
||||
}
|
||||
return colorsArr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate colors by chart type
|
||||
* @param chartType
|
||||
* @param index
|
||||
* @param count
|
||||
* @returns {Color}
|
||||
*/
|
||||
function getColors(chartType:string, index:number, count:number):Color {
|
||||
if (chartType === 'pie' || chartType === 'doughnut') {
|
||||
return formatPieColors(generateColors(count));
|
||||
}
|
||||
|
||||
if (chartType === 'polarArea') {
|
||||
return formatPolarAreaColors(generateColors(count));
|
||||
}
|
||||
|
||||
if (chartType === 'line' || chartType === 'radar') {
|
||||
return formatLineColor(generateColor(index));
|
||||
}
|
||||
|
||||
if (chartType === 'bar') {
|
||||
return formatBarColor(generateColor(index));
|
||||
}
|
||||
return generateColor(index);
|
||||
}
|
||||
|
||||
export const CHART_DIRECTIVES:Array<any> = [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%;
|
||||
}
|
||||
+6
-6
@@ -1,9 +1,9 @@
|
||||
<section class="time-series" ng-class="{ loading: !$ctrl.amplitudes }">
|
||||
<section class="time-series" [ngClass]="{ 'loading': !amplitudes }">
|
||||
<h2>Time Series</h2>
|
||||
<aside class="time-series-channels">
|
||||
<ul>
|
||||
<li ng-repeat="channel in $ctrl.channels track by $index"
|
||||
ng-style="{ 'color': $ctrl.colors[$index].strokeColor }">
|
||||
<li *ngFor="let channel of channels, let i = index"
|
||||
[ngStyle]="{ 'color': colors[i].borderColor }">
|
||||
{{ channel }}
|
||||
</li>
|
||||
</ul>
|
||||
@@ -11,14 +11,14 @@
|
||||
<canvas id="timeSeries" width="950" height="450"></canvas>
|
||||
<aside class="time-series-amplitudes">
|
||||
<ul>
|
||||
<li ng-repeat="amplitude in $ctrl.amplitudes track by $index"
|
||||
ng-style="{ 'color': $ctrl.colors[$index].strokeColor }">
|
||||
<li *ngFor="let amplitude of amplitudes, let i = index"
|
||||
[ngStyle]="{ 'color': colors[i].borderColor }">
|
||||
{{ amplitude }}
|
||||
</li>
|
||||
</ul>
|
||||
</aside>
|
||||
<footer class="time-series-duration">
|
||||
<time ng-repeat="time in $ctrl.timeline track by $index"
|
||||
<time *ngFor="let time of timeline"
|
||||
datetime="P1M">
|
||||
{{ time }}
|
||||
</time>
|
||||
@@ -0,0 +1,68 @@
|
||||
import { Component, ElementRef, OnInit, OnDestroy } from '@angular/core';
|
||||
import { SmoothieChart, TimeSeries } from 'smoothie';
|
||||
import * as io from 'socket.io-client';
|
||||
import { ChartService } from '../shared';
|
||||
|
||||
import { Constants } from '../shared/constants';
|
||||
|
||||
@Component({
|
||||
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].borderColor
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
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';
|
||||
@@ -1,14 +1,33 @@
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.topoplot-wrapper {
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
border: 3px solid black;
|
||||
:host {
|
||||
display: block;
|
||||
margin: 20px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 0 5px rgba(0,0,0,0.3);
|
||||
background-color: #333333;
|
||||
position: relative;
|
||||
height: 40vw;
|
||||
width: 40vw;
|
||||
}
|
||||
|
||||
h2 {
|
||||
position: absolute;
|
||||
margin: 0;
|
||||
top: 10px;
|
||||
right: 20px;
|
||||
font-weight: 300;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.topoplot {
|
||||
-webkit-clip-path: circle(50%);
|
||||
border-radius: 50%;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
[class*='topoplot-c'] {
|
||||
@@ -79,4 +98,4 @@
|
||||
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,55 @@
|
||||
import { Component, OnInit, OnDestroy, ElementRef } from '@angular/core';
|
||||
import * as io from 'socket.io-client';
|
||||
import { Constants } from '../shared/constants';
|
||||
import { ChartService } from '../shared';
|
||||
|
||||
declare var chroma: any;
|
||||
declare var Plotly: any;
|
||||
|
||||
@Component({
|
||||
selector: 'bci-topo',
|
||||
templateUrl: 'topo.component.html',
|
||||
styleUrls: ['topo.component.css'],
|
||||
providers: [Constants, ChartService]
|
||||
})
|
||||
|
||||
export class TopoComponent implements OnInit {
|
||||
|
||||
socket: any;
|
||||
plotElement: any;
|
||||
|
||||
constructor(private view: ElementRef, private chartService: ChartService, private constants: Constants) {
|
||||
this.socket = io(constants.socket.url);
|
||||
}
|
||||
|
||||
private data: any = this.chartService.getPlotlyContourData({
|
||||
x: this.getGrid(11),
|
||||
y: this.getGrid(11)
|
||||
});
|
||||
|
||||
private layout: any = this.chartService.getPlotlyContourLayout();
|
||||
|
||||
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) => {
|
||||
this.data.z = data.data;
|
||||
Plotly.redraw(this.plotElement);
|
||||
Plotly.Plots.resize(this.plotElement);
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy (): void {
|
||||
this.socket.removeListener(this.constants.socket.events.topo);
|
||||
}
|
||||
|
||||
getGrid (n) {
|
||||
return Array(n).fill(0).map((v, i) => i).reverse();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export const environment = {
|
||||
production: false
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
export const environment = {
|
||||
production: true
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
// The file for the current environment will overwrite this one during build.
|
||||
// Different environments can be found in ./environment.{dev|prod}.ts, and
|
||||
// you can create your own and use it with the --env flag.
|
||||
// The build system defaults to the dev environment.
|
||||
|
||||
export const environment = {
|
||||
production: false
|
||||
};
|
||||
Arquivo binário não exibido.
|
Depois Largura: | Altura: | Tamanho: 5.3 KiB |
@@ -0,0 +1,25 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>BCI Dashboard</title>
|
||||
<base href="/">
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
<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>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<bci-dashboard>Loading...</bci-dashboard>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,16 @@
|
||||
import './polyfills.ts';
|
||||
import 'chart.js';
|
||||
import 'chroma-js';
|
||||
import 'plotly.js';
|
||||
import 'brainbrowser/build/brainbrowser-2.3.0/brainbrowser.surface-viewer.min';
|
||||
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
import { enableProdMode } from '@angular/core';
|
||||
import { environment } from './environments/environment';
|
||||
import { DashboardModule } from './app/dashboard.module';
|
||||
|
||||
if (environment.production) {
|
||||
enableProdMode();
|
||||
}
|
||||
|
||||
platformBrowserDynamic().bootstrapModule(DashboardModule);
|
||||
@@ -0,0 +1,19 @@
|
||||
// This file includes polyfills needed by Angular 2 and is loaded before
|
||||
// the app. You can add your own extra polyfills to this file.
|
||||
import 'core-js/es6/symbol';
|
||||
import 'core-js/es6/object';
|
||||
import 'core-js/es6/function';
|
||||
import 'core-js/es6/parse-int';
|
||||
import 'core-js/es6/parse-float';
|
||||
import 'core-js/es6/number';
|
||||
import 'core-js/es6/math';
|
||||
import 'core-js/es6/string';
|
||||
import 'core-js/es6/date';
|
||||
import 'core-js/es6/array';
|
||||
import 'core-js/es6/regexp';
|
||||
import 'core-js/es6/map';
|
||||
import 'core-js/es6/set';
|
||||
import 'core-js/es6/reflect';
|
||||
|
||||
import 'core-js/es7/reflect';
|
||||
import 'zone.js/dist/zone';
|
||||
@@ -0,0 +1,28 @@
|
||||
'use strict';
|
||||
|
||||
const constants = require('./constants');
|
||||
const io = require('socket.io')(process.env.app_port || constants.sockets.port);
|
||||
|
||||
const Connectors = require('./connectors');
|
||||
const Providers = require('./providers');
|
||||
const Modules = require('./modules');
|
||||
|
||||
const Connector = new Connectors.Serialport({
|
||||
verbose: true
|
||||
});
|
||||
|
||||
const Signal = new Providers.Signal({ io });
|
||||
const Motion = new Providers.Motion({ io });
|
||||
|
||||
Connector.start().then(() => {
|
||||
const FFT = new Modules.FFT({ Signal });
|
||||
const Topo = new Modules.Topo({ Signal });
|
||||
const TimeSeries = new Modules.TimeSeries({ Signal });
|
||||
});
|
||||
|
||||
Connector.stream((data) => {
|
||||
Signal.buffer(data);
|
||||
Motion.capture(data);
|
||||
});
|
||||
|
||||
process.on(constants.events.terminate, Connector.stop);
|
||||
@@ -0,0 +1,6 @@
|
||||
|
||||
module.exports = class Bluetooth {
|
||||
/**
|
||||
* The future!
|
||||
*/
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
const Serialport = require('./serialport.connector');
|
||||
const Bluetooth = require('./bluetooth.connector');
|
||||
|
||||
module.exports = {
|
||||
Serialport,
|
||||
Bluetooth
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
'use strict';
|
||||
|
||||
const Utils = require('../utils');
|
||||
const OpenBCI = require('openbci-sdk');
|
||||
const OpenBCIBoard = OpenBCI.OpenBCIBoard;
|
||||
const constants = require('../constants');
|
||||
|
||||
module.exports = class Serialport extends OpenBCIBoard {
|
||||
|
||||
constructor (options) {
|
||||
super(options);
|
||||
}
|
||||
|
||||
start () {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
var onConnect = () => {
|
||||
this.on(constants.connector.readyEvent, () => {
|
||||
this.streamStart();
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
|
||||
this.autoFindOpenBCIBoard()
|
||||
.then((portName) => {
|
||||
if (portName) {
|
||||
this.connect(portName).then(onConnect);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
if (Utils.signal.isSimulated()) {
|
||||
this.connect(OpenBCI.OpenBCIConstants.OBCISimulatorPortName)
|
||||
.then(onConnect);
|
||||
} else {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
stop () {
|
||||
this.streamStop().then(() => {
|
||||
this.disconnect().then(() => {
|
||||
process.exit();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
stream (callback) {
|
||||
this.on(constants.connector.sampleEvent, callback);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
connector: {
|
||||
channels: 8,
|
||||
simulateFlag: 'simulate',
|
||||
readyEvent: 'ready',
|
||||
sampleEvent: 'sample'
|
||||
},
|
||||
signal: {
|
||||
bufferSize: 512,
|
||||
sampleRate: 250,
|
||||
windowSize: 32 // data has a moving window of 32 samples = 128 milliseconds (250Hz)
|
||||
},
|
||||
fft: {
|
||||
bins: 512
|
||||
},
|
||||
scale: {
|
||||
global: 1.5,
|
||||
simulated: 4,
|
||||
skipLabels: 4
|
||||
},
|
||||
units: {
|
||||
hertz: 'Hz',
|
||||
microvolts: 'uV',
|
||||
seconds: 's'
|
||||
},
|
||||
bands: { // frequency
|
||||
delta: [1, 3],
|
||||
theta: [4, 8],
|
||||
alpha: [8, 12],
|
||||
beta: [13, 30],
|
||||
gamma: [30, 100]
|
||||
},
|
||||
time: {
|
||||
windowSize: 5, // seconds
|
||||
timeline: 20, // seconds
|
||||
skip: 2 //
|
||||
},
|
||||
events: {
|
||||
fft: 'bci:fft',
|
||||
topo: 'bci:topo',
|
||||
time: 'bci:time',
|
||||
signal: 'bci:signal',
|
||||
filter: 'bci:filter',
|
||||
motion: 'bci:motion',
|
||||
terminate: 'SIGINT'
|
||||
},
|
||||
topo: {
|
||||
params: [0,10,11],
|
||||
x: [3,7,2,8,0,10,3,7],
|
||||
y: [0,0,3,3,8,8,10,10]
|
||||
},
|
||||
sockets: {
|
||||
port: 8080
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,76 @@
|
||||
'use strict';
|
||||
|
||||
const dsp = require('dsp.js');
|
||||
const Utils = require('../utils');
|
||||
const constants = require('../constants');
|
||||
|
||||
module.exports = class FFT {
|
||||
|
||||
constructor ({ Signal }) {
|
||||
this.signal = Signal;
|
||||
this.bins = constants.fft.bins;
|
||||
this.bufferSize = constants.signal.bufferSize;
|
||||
this.sampleRate = constants.signal.sampleRate;
|
||||
this.bands = constants.bands;
|
||||
this.spectrums = [[],[],[],[],[],[],[],[]];
|
||||
this.byBand = [];
|
||||
this.labels = [];
|
||||
this.subscribe();
|
||||
}
|
||||
|
||||
subscribe () {
|
||||
this.signal.emitter.on(constants.events.signal, (signals) => {
|
||||
this.signalsToFFT(signals);
|
||||
this.scaleLabels();
|
||||
this.filterBands();
|
||||
this.filterLabels();
|
||||
this.emit();
|
||||
});
|
||||
}
|
||||
|
||||
signalsToFFT (signals) {
|
||||
signals.forEach((signal, index) => {
|
||||
signal = Utils.filter.process(signal);
|
||||
let fft = new dsp.FFT(this.bufferSize, this.sampleRate);
|
||||
fft.forward(signal);
|
||||
this.spectrums[index] = Utils.data.parseObjectAsArray(fft.spectrum);
|
||||
this.spectrums[index] = Utils.signal.voltsToMicrovolts(this.spectrums[index], true);
|
||||
});
|
||||
}
|
||||
|
||||
scaleLabels () {
|
||||
this.labels = new Array(this.bins / 2).fill()
|
||||
.map((label, index) => {
|
||||
return Math.ceil(index * (this.sampleRate / this.bins));
|
||||
});
|
||||
}
|
||||
|
||||
filterBands () {
|
||||
for (let band in this.bands) {
|
||||
this.byBand[band] = Utils.filter.filterBand(this.spectrums, this.labels, this.bands[band]);
|
||||
}
|
||||
}
|
||||
|
||||
filterLabels () {
|
||||
// Skip every 8, add uni (too many labels issue)
|
||||
this.labels = this.labels.map((label, index, labels) => {
|
||||
let eighth = index % constants.scale.skipLabels === 0;
|
||||
let last = index === (labels.length - 1);
|
||||
return eighth || last ? `${label} ${constants.units.hertz}` : ``;
|
||||
});
|
||||
}
|
||||
|
||||
emit () {
|
||||
this.signal.io.emit(constants.events.fft, {
|
||||
data: this.spectrums,
|
||||
labels: this.labels,
|
||||
theta: this.byBand.theta.spectrums,
|
||||
delta: this.byBand.delta.spectrums,
|
||||
alpha: this.byBand.alpha.spectrums,
|
||||
beta: this.byBand.beta.spectrums,
|
||||
gamma: this.byBand.gamma.spectrums
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
'use strict';
|
||||
|
||||
const FFT = require('./fft.module');
|
||||
const TimeSeries = require('./time-series.module');
|
||||
const Topo = require('./topo.module');
|
||||
|
||||
module.exports = {
|
||||
FFT,
|
||||
TimeSeries,
|
||||
Topo
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
'use strict';
|
||||
|
||||
const Utils = require('../utils');
|
||||
const constants = require('../constants');
|
||||
|
||||
module.exports = class TimeSeries {
|
||||
|
||||
constructor ({ Signal }) {
|
||||
this.signal = Signal;
|
||||
this.sampleRate = constants.signal.sampleRate;
|
||||
this.bufferSize = constants.signal.bufferSize;
|
||||
this.windowSize = constants.signal.windowSize;
|
||||
this.timeline = Utils.data.generateTimeline(constants.time.timeline, constants.time.skip, constants.units.seconds);
|
||||
this.timeSeries = [];
|
||||
this.amplitudes = [];
|
||||
this.subscribe();
|
||||
}
|
||||
|
||||
subscribe () {
|
||||
this.signal.emitter.on(constants.events.signal, (signal) => {
|
||||
this.timeSeries = signal;
|
||||
this.filter();
|
||||
this.offset();
|
||||
this.trim();
|
||||
this.signalToAmplitudes(signal);
|
||||
this.emit();
|
||||
});
|
||||
}
|
||||
|
||||
offset () {
|
||||
this.timeSeries = this.timeSeries.map((channel, channelIndex) => {
|
||||
return channel.map((amplitude) => {
|
||||
return Utils.signal.offsetForGrid(amplitude, channelIndex, constants.connector.channels, this.signal.scale);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
filter () {
|
||||
this.timeSeries.forEach((signal) => {
|
||||
signal = Utils.filter.highpass(signal);
|
||||
});
|
||||
}
|
||||
|
||||
trim () {
|
||||
this.timeSeries.forEach((channel) => {
|
||||
channel = channel.splice(0, this.bufferSize - this.windowSize);
|
||||
});
|
||||
}
|
||||
|
||||
signalToAmplitudes (signal) {
|
||||
this.amplitudes = signal.map((channel) => {
|
||||
let microvolts = Utils.signal.voltsToMicrovolts(channel[channel.length - 1])[0];
|
||||
return `${Math.round(microvolts)} ${constants.units.microvolts}`;
|
||||
});
|
||||
}
|
||||
|
||||
emit () {
|
||||
this.signal.io.emit(constants.events.time, {
|
||||
data: this.timeSeries,
|
||||
amplitudes: this.amplitudes,
|
||||
timeline: this.timeline
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
'use strict';
|
||||
|
||||
const topogrid = require('topogrid');
|
||||
const Utils = require('../utils');
|
||||
const constants = require('../constants');
|
||||
|
||||
module.exports = class Topo {
|
||||
|
||||
constructor ({ Signal }) {
|
||||
this.signal = Signal;
|
||||
this.sampleRate = constants.signal.sampleRate;
|
||||
this.grid = [];
|
||||
this.subscribe();
|
||||
}
|
||||
|
||||
subscribe () {
|
||||
this.signal.emitter.on(constants.events.signal, (signal) => {
|
||||
this.signalToGrid(signal);
|
||||
this.emit();
|
||||
});
|
||||
}
|
||||
|
||||
signalToGrid (signal) {
|
||||
|
||||
let grid = [];
|
||||
|
||||
signal.forEach((channel) => {
|
||||
grid.push(channel[channel.length - 1]);
|
||||
});
|
||||
|
||||
/**
|
||||
* params: 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
|
||||
* x: coordinates of the data
|
||||
* y: coordinates of the data
|
||||
* grid: data = [10,0,0,0,0,0,-10,30,25]; // the data values
|
||||
*/
|
||||
this.grid = topogrid.create(
|
||||
constants.topo.x,
|
||||
constants.topo.y,
|
||||
grid,
|
||||
constants.topo.params
|
||||
);
|
||||
}
|
||||
|
||||
emit () {
|
||||
this.signal.io.emit(constants.events.topo, {
|
||||
data: this.grid
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
const Signal = require('./signal.provider');
|
||||
const Motion = require('./motion.provider');
|
||||
|
||||
module.exports = {
|
||||
Signal,
|
||||
Motion
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
'use strict';
|
||||
|
||||
const constants = require('../constants');
|
||||
const Utils = require('../utils');
|
||||
|
||||
module.exports = class Motion {
|
||||
|
||||
constructor ({ io }) {
|
||||
this.io = io;
|
||||
this.auxData = [];
|
||||
this.count = 0;
|
||||
}
|
||||
|
||||
capture ({ auxData }) {
|
||||
|
||||
if (Utils.signal.isSimulated()) {
|
||||
this.simulate();
|
||||
} else {
|
||||
if (this.hasMotion(auxData)) {
|
||||
this.auxData = this.amplify(auxData);
|
||||
this.emit();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
hasMotion (auxData) {
|
||||
return auxData.reduce((a, b) => a + b, 0);
|
||||
}
|
||||
|
||||
amplify (auxData) {
|
||||
return auxData.map(axis => Math.round(axis));
|
||||
}
|
||||
|
||||
simulate () {
|
||||
this.count++;
|
||||
|
||||
if (this.count === 10) {
|
||||
this.auxData = [
|
||||
Math.round(Math.random() * 180),
|
||||
Math.round(Math.random() * 180),
|
||||
Math.round(Math.random() * 180)
|
||||
];
|
||||
this.count = 0;
|
||||
|
||||
|
||||
this.emit();
|
||||
}
|
||||
}
|
||||
|
||||
emit () {
|
||||
this.io.emit(constants.events.motion, {
|
||||
data: this.auxData
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
'use strict';
|
||||
|
||||
const EventEmitter = require('events');
|
||||
const Utils = require('../utils');
|
||||
const constants = require('../constants');
|
||||
|
||||
class SignalEmitter extends EventEmitter {}
|
||||
|
||||
module.exports = class Signal {
|
||||
|
||||
constructor ({ io }) {
|
||||
this.io = io;
|
||||
this.emitter = new SignalEmitter();
|
||||
this.bufferSize = constants.signal.bufferSize;
|
||||
this.windowSize = constants.signal.windowSize;
|
||||
this.sampleRate = constants.signal.sampleRate;
|
||||
this.signals = [[],[],[],[],[],[],[],[]];
|
||||
this.sampleNumber = 0;
|
||||
this.init();
|
||||
}
|
||||
|
||||
init () {
|
||||
this.io.on('connection', (socket) => {
|
||||
socket.on(constants.events.filter, (filter) => {
|
||||
Utils.filter.apply(filter);
|
||||
});
|
||||
});
|
||||
|
||||
this.setScale();
|
||||
}
|
||||
|
||||
buffer (sample) {
|
||||
this.sampleNumber++;
|
||||
this.add(sample);
|
||||
|
||||
if (this.sampleNumber === this.bufferSize) {
|
||||
this.emitter.emit(constants.events.signal, [...this.signals]);
|
||||
this.window();
|
||||
}
|
||||
}
|
||||
|
||||
add (sample) {
|
||||
//console.log('sample', sample);
|
||||
Object.keys(sample.channelData).forEach((channel, i) => {
|
||||
this.signals[i].push(sample.channelData[channel]);
|
||||
});
|
||||
}
|
||||
|
||||
window () {
|
||||
this.signals = this.signals.map((channel) => {
|
||||
return channel.filter((signal, index) => {
|
||||
return index > (this.windowSize - 1);
|
||||
});
|
||||
});
|
||||
this.sampleNumber = this.bufferSize - this.windowSize;
|
||||
}
|
||||
|
||||
setScale () {
|
||||
if (Utils.signal.isSimulated()) {
|
||||
this.scale = constants.scale.simulated;
|
||||
} else {
|
||||
this.scale = constants.scale.global;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
|
||||
parseObjectAsArray (obj) {
|
||||
var array = [];
|
||||
Object.keys(obj).forEach((key) => {
|
||||
array.push(obj[key]);
|
||||
});
|
||||
return array;
|
||||
},
|
||||
|
||||
/**
|
||||
* generateTimeline
|
||||
* @param size
|
||||
* @param skip
|
||||
* @param suffix
|
||||
* @returns {Array.<T>}
|
||||
*/
|
||||
generateTimeline (size, skip, suffix) {
|
||||
return new Array(size)
|
||||
.fill()
|
||||
.map((value, index) => index)
|
||||
.filter((value, index) => index % skip === 0)
|
||||
.map((value) => (value ? '-' : '') + value + suffix)
|
||||
.reverse();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
'use strict';
|
||||
|
||||
var Fili = require('fili');
|
||||
|
||||
module.exports = {
|
||||
|
||||
state: {
|
||||
BANDPASS: '1-50',
|
||||
NOTCH: '60',
|
||||
VERTSCALE: '50',
|
||||
VERTALGO: 'LOG',
|
||||
SMOOTH: '0-75',
|
||||
POLARITY: 'YES',
|
||||
MAXFREQUENCY: '60'
|
||||
},
|
||||
|
||||
apply (filter) {
|
||||
if (!filter) return;
|
||||
let [id, value] = filter.split(':');
|
||||
this.state[id] = value;
|
||||
},
|
||||
|
||||
process (signal) {
|
||||
Object.keys(this.state).forEach((key) => {
|
||||
let filter = key.toLowerCase();
|
||||
if (filter in this && typeof this[filter] === 'function') {
|
||||
// @TODO: apply all filters dynamically
|
||||
//signal = this[filter](signal);
|
||||
}
|
||||
});
|
||||
signal = this.notch(signal);
|
||||
return signal;
|
||||
},
|
||||
|
||||
highpass (signal) {
|
||||
|
||||
var iirCalculator = new Fili.CalcCascades();
|
||||
|
||||
var hpFilterCoeffs = iirCalculator.highpass({
|
||||
order: 4, // cascade 4 biquad filters (max: 12)
|
||||
characteristic: 'butterworth',
|
||||
Fs: 250, // 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);
|
||||
|
||||
return hpFilter.multiStep(signal);
|
||||
|
||||
},
|
||||
|
||||
notch (signal) {
|
||||
|
||||
if (this.state.NOTCH === 'NONE') return signal;
|
||||
|
||||
var notchValue = parseInt(this.state.NOTCH);
|
||||
var iirCalculator = new Fili.CalcCascades();
|
||||
var notchFilterCoeffs = iirCalculator.bandstop({
|
||||
order: 2, // cascade 3 biquad filters (max: 12)
|
||||
characteristic: 'butterworth',
|
||||
Fs: 250, // sampling frequency
|
||||
Fc: notchValue,
|
||||
F1: notchValue - 1,
|
||||
F2: notchValue + 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 notchFilter = new Fili.IirFilter(notchFilterCoeffs);
|
||||
|
||||
return notchFilter.multiStep(signal);
|
||||
},
|
||||
|
||||
bandpass (signal) {
|
||||
|
||||
// @TODO: Finish bandpass filter
|
||||
return filter;
|
||||
|
||||
var iirCalculator = new Fili.CalcCascades();
|
||||
|
||||
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);
|
||||
|
||||
// @TODO: get from state which filter to use, then return it based on state settings
|
||||
},
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
'use strict';
|
||||
|
||||
const data = require('./data.util');
|
||||
const filter = require('./filter.util');
|
||||
const signal = require('./signal.util');
|
||||
|
||||
module.exports = {
|
||||
data,
|
||||
filter,
|
||||
signal
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
'use strict';
|
||||
|
||||
var argv = require('yargs').argv;
|
||||
const constants = require('../constants');
|
||||
|
||||
module.exports = {
|
||||
|
||||
voltsToMicrovolts (volts, log) {
|
||||
if (!Array.isArray(volts)) {
|
||||
volts = [volts];
|
||||
}
|
||||
return volts.map((volt) => {
|
||||
return log ? Math.log10(Math.pow(10, 6) * volt) : Math.pow(10, 6) * volt;
|
||||
});
|
||||
},
|
||||
|
||||
offsetForGrid (amplitude, channelNumber, channelAmount = 8, scale = 1.5) {
|
||||
let scaledAmplitude = amplitude * Math.pow(10, scale);
|
||||
let offset = 2 * (channelAmount - channelNumber) - 1;
|
||||
return parseFloat(scaledAmplitude + offset);
|
||||
},
|
||||
|
||||
isSimulated () {
|
||||
return !!(argv._[0] && argv._[0] === constants.connector.simulateFlag);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
/* You can add global styles to this file, and also import other style files */
|
||||
@@ -0,0 +1,32 @@
|
||||
import './polyfills.ts';
|
||||
|
||||
import 'zone.js/dist/long-stack-trace-zone';
|
||||
import 'zone.js/dist/jasmine-patch';
|
||||
import 'zone.js/dist/async-test';
|
||||
import 'zone.js/dist/fake-async-test';
|
||||
import 'zone.js/dist/sync-test';
|
||||
|
||||
// Unfortunately there's no typing for the `__karma__` variable. Just declare it as any.
|
||||
declare var __karma__: any;
|
||||
|
||||
// Prevent Karma from running prematurely.
|
||||
__karma__.loaded = function () {};
|
||||
|
||||
|
||||
Promise.all([
|
||||
System.import('@angular/core/testing'),
|
||||
System.import('@angular/platform-browser-dynamic/testing')
|
||||
])
|
||||
// First, initialize the Angular testing environment.
|
||||
.then(([testing, testingBrowser]) => {
|
||||
testing.getTestBed().initTestEnvironment(
|
||||
testingBrowser.BrowserDynamicTestingModule,
|
||||
testingBrowser.platformBrowserDynamicTesting()
|
||||
);
|
||||
})
|
||||
// Then we find all the tests.
|
||||
.then(() => require.context('./', true, /\.spec\.ts/))
|
||||
// And load the modules.
|
||||
.then(context => context.keys().map(context))
|
||||
// Finally, start Karma to run the tests.
|
||||
.then(__karma__.start, __karma__.error);
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"declaration": false,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"lib": ["es6", "dom"],
|
||||
"mapRoot": "./",
|
||||
"module": "es6",
|
||||
"moduleResolution": "node",
|
||||
"outDir": "../dist/out-tsc",
|
||||
"sourceMap": true,
|
||||
"target": "es5",
|
||||
"typeRoots": [
|
||||
"../node_modules/@types"
|
||||
]
|
||||
}
|
||||
}
|
||||
externo
+5
@@ -0,0 +1,5 @@
|
||||
// Typings reference file, see links for more information
|
||||
// https://github.com/typings/typings
|
||||
// https://www.typescriptlang.org/docs/handbook/writing-declaration-files.html
|
||||
|
||||
declare var System: any;
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
{
|
||||
"rulesDirectory": [
|
||||
"node_modules/codelyzer"
|
||||
],
|
||||
"rules": {
|
||||
"class-name": true,
|
||||
"comment-format": [
|
||||
true,
|
||||
"check-space"
|
||||
],
|
||||
"curly": true,
|
||||
"eofline": true,
|
||||
"forin": true,
|
||||
"indent": [
|
||||
true,
|
||||
"spaces"
|
||||
],
|
||||
"label-position": true,
|
||||
"label-undefined": true,
|
||||
"max-line-length": [
|
||||
true,
|
||||
140
|
||||
],
|
||||
"member-access": false,
|
||||
"member-ordering": [
|
||||
true,
|
||||
"static-before-instance",
|
||||
"variables-before-functions"
|
||||
],
|
||||
"no-arg": true,
|
||||
"no-bitwise": true,
|
||||
"no-console": [
|
||||
true,
|
||||
"debug",
|
||||
"info",
|
||||
"time",
|
||||
"timeEnd",
|
||||
"trace"
|
||||
],
|
||||
"no-construct": true,
|
||||
"no-debugger": true,
|
||||
"no-duplicate-key": true,
|
||||
"no-duplicate-variable": true,
|
||||
"no-empty": false,
|
||||
"no-eval": true,
|
||||
"no-inferrable-types": true,
|
||||
"no-shadowed-variable": true,
|
||||
"no-string-literal": false,
|
||||
"no-switch-case-fall-through": true,
|
||||
"no-trailing-whitespace": true,
|
||||
"no-unused-expression": true,
|
||||
"no-unused-variable": true,
|
||||
"no-unreachable": true,
|
||||
"no-use-before-declare": true,
|
||||
"no-var-keyword": true,
|
||||
"object-literal-sort-keys": false,
|
||||
"one-line": [
|
||||
true,
|
||||
"check-open-brace",
|
||||
"check-catch",
|
||||
"check-else",
|
||||
"check-whitespace"
|
||||
],
|
||||
"quotemark": [
|
||||
true,
|
||||
"single"
|
||||
],
|
||||
"radix": true,
|
||||
"semicolon": [
|
||||
"always"
|
||||
],
|
||||
"triple-equals": [
|
||||
true,
|
||||
"allow-null-check"
|
||||
],
|
||||
"typedef-whitespace": [
|
||||
true,
|
||||
{
|
||||
"call-signature": "nospace",
|
||||
"index-signature": "nospace",
|
||||
"parameter": "nospace",
|
||||
"property-declaration": "nospace",
|
||||
"variable-declaration": "nospace"
|
||||
}
|
||||
],
|
||||
"variable-name": false,
|
||||
"whitespace": [
|
||||
true,
|
||||
"check-branch",
|
||||
"check-decl",
|
||||
"check-operator",
|
||||
"check-separator",
|
||||
"check-type"
|
||||
],
|
||||
|
||||
"directive-selector-prefix": [true, "app"],
|
||||
"component-selector-prefix": [true, "app"],
|
||||
"directive-selector-name": [true, "camelCase"],
|
||||
"component-selector-name": [true, "kebab-case"],
|
||||
"directive-selector-type": [true, "attribute"],
|
||||
"component-selector-type": [true, "element"],
|
||||
"use-input-property-decorator": true,
|
||||
"use-output-property-decorator": true,
|
||||
"use-host-property-decorator": true,
|
||||
"no-input-rename": true,
|
||||
"no-output-rename": true,
|
||||
"use-life-cycle-interface": true,
|
||||
"use-pipe-transform-interface": true,
|
||||
"component-class-suffix": true,
|
||||
"directive-class-suffix": true
|
||||
}
|
||||
}
|
||||
Alguns arquivos não foram exibidos porque demasiados arquivos foram alterados neste diff Mostrar Mais
Referência em uma Nova Issue
Bloquear um usuário