107 Commits

Autor SHA1 Mensagem Data
Alex Castillo 3c748e8152 fili fft 2016-06-12 17:25:46 -04:00
Alex Castillo aa7a5c78f2 time series improvements 2016-06-12 16:12:14 -04:00
Alex Castillo ff0b0192bb signals improvements 2016-06-12 16:09:13 -04:00
Alex Castillo 2803c3b829 upgrade to ng2-charts/chart.js v2
Closes #34
2016-06-05 20:13:39 -04:00
Alex Castillo 6e1fc861e7 Merge branch 'motion' 2016-06-03 12:46:52 -04:00
Alex Castillo d96fbabef8 motion progress 2016-06-03 12:46:07 -04:00
Alex Castillo ed260da6ee add motion component
Closes 31
2016-06-03 07:42:25 -04:00
Alex Castillo 4cd9a641ce dynamic global scale
Closes #27
2016-06-01 22:43:51 -04:00
Alex Castillo 9601ff89a1 add constants to back-end
Closes #24
2016-06-01 22:09:42 -04:00
Alex Castillo 2942d8a3d0 refactor time series server code for filters
Closes #14
2016-05-31 22:30:01 -04:00
Alex Castillo b3f41b00de create connector class
Closes #25
2016-05-31 21:59:54 -04:00
Alex Castillo 8b7e60ca35 upgrade to node.js v6
Closes #22
2016-05-31 21:59:54 -04:00
Alex Castillo 7661dad72e refactor back-end
Closes #23
2016-05-31 21:59:54 -04:00
Alex Castillo cc3eaf834b add too assets 2016-05-31 21:59:54 -04:00
Alex Castillo 8351474fbc update readme 2016-05-19 16:56:09 -04:00
Alex Castillo 005950b173 package.json add email 2016-05-18 22:08:36 -04:00
Alex Castillo 847c1ff511 update package.json 2016-05-18 22:07:18 -04:00
Alex Castillo 44ed5c8fa5 update readme 2016-05-18 22:04:06 -04:00
Alex Castillo 44f7a035c8 make time series default route 2016-05-18 21:59:59 -04:00
Alex Castillo ef761dd32b update readme 2016-05-17 19:56:44 -04:00
Alex Castillo c2bad61f92 mask topo plot
includes clean-up
2016-05-17 19:56:05 -04:00
Alex Castillo 74c8fb1a68 update contour plot 2016-05-17 18:44:46 -04:00
Alex Castillo 36986b66a6 style fixes 2016-05-15 19:56:39 -04:00
Alex Castillo 98ebbd83fd add filters component
includes notch filter
2016-05-15 18:52:09 -04:00
Alex Castillo 118b480690 topo experiment 2016-05-15 11:08:08 -04:00
Alex Castillo e3e9974888 topo via plotly 2016-05-14 19:21:25 -04:00
Alex Castillo 0c237e0116 add assets for readme 2016-05-14 19:01:54 -04:00
Alex Castillo bd534b3216 add default route 2016-05-13 16:54:53 -04:00
Alex Castillo 7393c0603b migrate topo component/route 2016-05-13 16:42:23 -04:00
Alex Castillo 17ef476a32 destroy socket event listener on component deactivate 2016-05-13 15:54:48 -04:00
Alex Castillo ef5857e3a2 add constants and move defaults to service 2016-05-13 15:51:09 -04:00
Alex Castillo 92f27fb400 add frequency bands component/route 2016-05-13 15:24:02 -04:00
Alex Castillo 1e4401c68b abstract frequency chart for various types
+add radar chart
2016-05-13 13:05:06 -04:00
Alex Castillo 64460c3fe6 style fixes 2016-05-13 12:21:43 -04:00
Alex Castillo 4de6d83bf4 migrate to angular2 2016-05-11 10:23:16 -04:00
Alex Castillo efe6fe2829 readme updated 2016-05-09 20:52:51 -04:00
Alex Castillo 7810048145 add npm start script 2016-05-08 13:31:12 -06:00
Alex Castillo 77d62d797f remove duplicate charts property 2016-05-07 16:28:19 -06:00
Alex Castillo a0fc9925d9 add repository and bugs to package.json 2016-05-06 07:25:08 -06:00
Alex Castillo b1fa763845 update package keywords 2016-05-06 07:19:49 -06:00
Alex Castillo 0329ea5703 Merge pull request #18 from NeuroJS/frequency-bands
Closes #17: add gamma to the frequency plot
2016-05-04 23:07:51 -06:00
Alex Castillo 1640a96e95 remove filtered signals 2016-05-04 23:07:04 -06:00
Alex Castillo 60c0ab004c filters fixes 2016-05-04 15:17:23 -06:00
Alex Castillo 5e4ea5b750 Merge pull request #20 from teonbrooks/signalFiltered
Remove filtering from spectrum plots
2016-05-04 14:20:12 -06:00
Teon L Brooks 3fece01fd4 Remove filtering from spectrum plots
- Remove hard-coded sample rate.
- Separated the filtering from the fft
2016-05-04 15:57:08 -04:00
Alex Castillo 39de35ea28 added missing directive 2016-05-04 13:22:25 -06:00
Alex Castillo 6f9a5a260c Closes #17: add gamma to the frequency plot 2016-05-04 11:58:11 -06:00
Alex Castillo 29777e0770 Merge pull request #13 from andrewheusser/filter
bandpass, highpass, lowpass
2016-05-04 11:25:49 -06:00
andrewheusser 3ff08885f3 :) 2016-05-04 09:33:11 -04:00
andrewheusser f76ac21401 topo params 2016-05-04 09:32:19 -04:00
andrewheusser 5e41f2328b added high and low pass 2016-05-03 19:37:20 -04:00
andrewheusser 8041e70ac3 quick patch 2016-05-03 18:43:16 -04:00
andrewheusser 1c9682351c bandstop filter implemented 2016-05-03 18:38:30 -04:00
andrewheusser f41b60515f topo plot patch
topo data is now raw signal and emits on each sample. can someone test
on real data?
2016-05-03 09:48:46 -04:00
Alex Castillo 544dec0433 fix and upgrade packages 2016-05-03 02:27:33 -04:00
Alex Castillo 29382c8369 angular style guide part 2 2016-05-02 19:53:40 -04:00
Alex Castillo 29903ceabf apply style guide to components 2016-05-01 20:01:43 -04:00
Alex Castillo 03312d6f14 refactor time series directive 2016-05-01 19:39:50 -04:00
Alex Castillo 474965a679 add channel labels to time series 2016-05-01 19:11:58 -04:00
Alex Castillo abc3631d06 abbr channel lables 2016-05-01 18:49:59 -04:00
Alex Castillo ba9b5cbd04 label fixes for bands 2016-05-01 18:45:48 -04:00
Alex Castillo f53868166b move label filter to node, add unit 2016-05-01 18:39:33 -04:00
Alex Castillo bcd225a5fb add units to time series 2016-05-01 18:12:15 -04:00
Alex Castillo 33e9dad276 remove board disconnect workaround 2016-05-01 12:43:34 -04:00
Alex Castillo f9b1e50ebe upgrade openbci-sdk 2016-05-01 12:38:35 -04:00
Alex Castillo dc3deb6bf9 generate timeline 2016-05-01 12:11:01 -04:00
Alex Castillo 580e3fc93e remove non-performing time series 2016-05-01 11:43:27 -04:00
Alex Castillo c6d5c0e210 frequency chart types consolidated 2016-04-30 11:08:16 -04:00
Alex Castillo 8cef10d64a update readme 2016-04-29 23:20:59 -04:00
Alex Castillo 28114d108a preview updated 2016-04-29 23:18:19 -04:00
Alex Castillo b63405d3b3 add frequency radar chart 2016-04-29 23:17:23 -04:00
Alex Castillo d6fac735cb chart size tweaks 2016-04-29 22:57:19 -04:00
Alex Castillo 1ee9de08c8 add loading 2016-04-29 22:07:13 -04:00
Alex Castillo 6d0859579a ignore transpiled files 2016-04-29 21:56:16 -04:00
Alex Castillo 782af8c06a increase global scale 2016-04-29 18:48:04 -04:00
Alex Castillo be7b68acb1 move timeline and add amplitude 2016-04-29 18:04:44 -04:00
Alex Castillo ead11736ca time series clean-up 2016-04-29 16:45:16 -04:00
Alex Castillo 4c851dd637 time series enhancements 2016-04-29 16:14:05 -04:00
Alex Castillo 0b6bec2ef0 timer series fixes 2016-04-29 15:07:06 -04:00
Alex Castillo eb6a072964 remove scale override for frequency bands 2016-04-29 12:54:51 -04:00
Alex Castillo 3c79f2e3b0 initial new time series based on smoothie charts 2016-04-29 12:47:57 -04:00
Alex Castillo a09fa51221 Merge pull request #9 from andrewheusser/master
bar chart filtering moved to backend
2016-04-29 10:37:59 -04:00
andrewheusser 1da42774a4 bar chart filtering moved to backend
-sockets now emits band filtered data instead of doing on the front end
deleted EEGUtils library from front end and put the function in
visualizer.js on the server
2016-04-28 18:47:57 -04:00
Alex Castillo 93ae6b5627 Merge pull request #8 from andrewheusser/master
topo now plots mean of frequency spectrum rather that samples
2016-04-28 10:16:19 -04:00
andrewheusser 46c45645df topo now plots mean of frequency spectrum rather that samples 2016-04-28 10:00:26 -04:00
Alex Castillo 1725f2d0b8 add navigation
socket event destroy

add options to frequency band charts

frequency mode by default

better scale for frequency bands
2016-04-27 22:24:44 -04:00
Alex Castillo 89705f9e64 Merge pull request #6 from andrewheusser/master
topoplot works!
2016-04-25 13:03:52 -04:00
Alex Castillo 9294f853c3 update readme and preview 2016-04-23 15:08:52 -04:00
andrewheusser 56f9d91560 topoplot works!
-coordinates are are rough estimates.  may want to change this, or make
them user definable.

-colors can be customized as well..using chroma to map from numbers to
color values
2016-04-23 11:30:17 -04:00
Alex Castillo b7e694f62b filter fix 2016-04-22 15:08:12 -04:00
Alex Castillo 8cfffd2577 various fixes 2016-04-22 15:06:53 -04:00
Alex Castillo 75c262f1f3 Merge pull request #3 from andrewheusser/topo
topo first draft
2016-04-22 14:33:34 -04:00
Alex Castillo 901b472ea1 fewer labels on axes
Closes #2
2016-04-22 14:31:57 -04:00
andrewheusser 84c199b0db Merge remote-tracking branch 'origin/topo' into topo
# Conflicts:
#	app/components/time/time.js
#	visualizer.js
2016-04-22 14:29:48 -04:00
andrewheusser 1571d22e9b topo first draft 2016-04-22 14:28:03 -04:00
Alex Castillo 538bb08632 add time series duration 2016-04-22 14:04:06 -04:00
Alex Castillo a604e4943c module rename 2016-04-22 13:39:25 -04:00
andrewheusser f61c0c2637 topo first draft 2016-04-22 11:02:34 -04:00
Alex Castillo 1e9ddf63e5 time series scale override 2016-04-22 09:31:10 -04:00
Alex Castillo d0536238b5 time series chart fix 2016-04-22 09:06:34 -04:00
Alex Castillo 43fb52bb88 custom colors 2016-04-21 23:11:30 -04:00
Alex Castillo b0102288cd socket events rename 2016-04-21 23:03:03 -04:00
Alex Castillo 7e1a2ecd5e Preview updated 2016-04-16 14:21:22 -04:00
Alex Castillo c7250e5932 Some basic styles 2016-04-15 16:58:27 -04:00
Alex Castillo 1ff37171e7 Fixing port number on readme 2016-04-15 15:43:57 -04:00
Alex Castillo 5a102f5f28 Adding time series logic 2016-04-15 15:43:02 -04:00
Alex Castillo 96ca12aaf0 revert to angular 1 2016-04-15 15:13:25 -04:00
114 arquivos alterados com 249066 adições e 428 exclusões
+3
Ver Arquivo
@@ -0,0 +1,3 @@
Language: JavaScript
BasedOnStyle: Google
ColumnLimit: 100
+14
Ver Arquivo
@@ -0,0 +1,14 @@
# http://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
max_line_length = 0
trim_trailing_whitespace = false
+28 -2
Ver Arquivo
@@ -1,2 +1,28 @@
.idea
node_modules
# See http://help.github.com/ignore-files/ for more about ignoring files.
# compiled output
/dist
/tmp
# dependencies
/node_modules
/bower_components
# IDEs and editors
/.idea
# misc
/.sass-cache
/connect.lock
/coverage/*
/libpeerconnection.log
npm-debug.log
testem.log
/typings
# e2e
/e2e/*.js
/e2e/*.map
#System Files
.DS_Store
+19 -5
Ver Arquivo
@@ -1,6 +1,8 @@
# OpenBCI Visualizer
# OpenBCI Dashboard
A fullstack javascript app for capturing and visualizing OpenBCI EEG data
![alt text](/assets/preview3.gif "OpenBCI Dashboard Preview")
A fullstack JavaScript app for reading and visualizing OpenBCI EEG data.
This project is under development, this is just a first draft.
@@ -10,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 visualizer
npm run visualize
```
* Go to: http://localhost:3036
* Go to: http://localhost:4200
## Simulating data
```bash
node visualizer simulate
npm run simulate
```
## Technologies
* Node.js
* Angular 2
* Socket.io
* Data Visualization
- Plotly.js
- Chart.js
- Smoothie
## Support
Pull requests are welcomed!
+24
Ver Arquivo
@@ -0,0 +1,24 @@
/* global require, module */
var Angular2App = require('angular-cli/lib/broccoli/angular2-app');
module.exports = function(defaults) {
return new Angular2App(defaults, {
vendorNpmFiles: [
'socket.io-client/socket.io.js',
'smoothie/smoothie.js',
'chart.js/dist/Chart.bundle.js',
'ng2-charts/bundles/ng2-charts.js',
'chroma-js/chroma.js',
'plotly.js/dist/plotly.js',
'brainbrowser/build/brainbrowser-2.3.0/**/*.js',
'systemjs/dist/system-polyfills.js',
'systemjs/dist/system.src.js',
'zone.js/dist/*.js',
'es6-shim/es6-shim.js',
'reflect-metadata/*.js',
'rxjs/**/*.js',
'@angular/**/*.js'
]
});
};
+25
Ver Arquivo
@@ -0,0 +1,25 @@
{
"project": {
"version": "0.1.0",
"name": "clitest"
},
"apps": [
{"main": "src/main.ts", "tsconfig": "src/tsconfig.json"}
],
"addons": [],
"packages": [],
"e2e": {
"protractor": {
"config": "config/protractor.conf.js"
}
},
"test": {
"karma": {
"config": "config/karma.conf.js"
}
},
"defaults": {
"prefix": "bci",
"sourceDir": "src"
}
}
-9
Ver Arquivo
@@ -1,9 +0,0 @@
<section>
<h2>Alpha</h2>
<canvas id="alpha"
class="chart chart-bar"
chart-data="$ctrl.data"
chart-labels="$ctrl.series"
chart-series="$ctrl.series">
</canvas>
</section>
-17
Ver Arquivo
@@ -1,17 +0,0 @@
angular.module('openbciVisualizer')
.component('bciAlpha', {
templateUrl: 'components/alpha/alpha.html',
controller: function ($timeout) {
var $ctrl = this;
var socket = io();
$ctrl.series = ['Channel 1','Channel 2','Channel 3','Channel 4','Channel 5','Channel 6','Channel 7','Channel 8'];
socket.on('openBCIData', function (data) {
var alphaRange = EEGSpectrumUtils.filterBand(data.spectrums.data, data.spectrums.labels, [8, 12]);
$timeout(function () {
$ctrl.labels = alphaRange.labels;
$ctrl.data = alphaRange.spectrums;
});
});
}
});
-9
Ver Arquivo
@@ -1,9 +0,0 @@
<section>
<h2>Beta</h2>
<canvas id="beta"
class="chart chart-bar"
chart-data="$ctrl.data"
chart-labels="$ctrl.series"
chart-series="$ctrl.series">
</canvas>
</section>
-17
Ver Arquivo
@@ -1,17 +0,0 @@
angular.module('openbciVisualizer')
.component('bciBeta', {
templateUrl: 'components/beta/beta.html',
controller: function ($timeout) {
var $ctrl = this;
var socket = io();
$ctrl.series = ['Channel 1','Channel 2','Channel 3','Channel 4','Channel 5','Channel 6','Channel 7','Channel 8'];
socket.on('openBCIData', function (data) {
var betaRange = EEGSpectrumUtils.filterBand(data.spectrums.data, data.spectrums.labels, [12, 40]);
$timeout(function () {
$ctrl.labels = betaRange.labels;
$ctrl.data = betaRange.spectrums;
});
});
}
});
-9
Ver Arquivo
@@ -1,9 +0,0 @@
<section>
<h2>Delta</h2>
<canvas id="delta"
class="chart chart-bar"
chart-data="$ctrl.data"
chart-labels="$ctrl.series"
chart-series="$ctrl.series">
</canvas>
</section>
-17
Ver Arquivo
@@ -1,17 +0,0 @@
angular.module('openbciVisualizer')
.component('bciDelta', {
templateUrl: 'components/delta/delta.html',
controller: function ($timeout) {
var $ctrl = this;
var socket = io();
$ctrl.series = ['Channel 1','Channel 2','Channel 3','Channel 4','Channel 5','Channel 6','Channel 7','Channel 8'];
socket.on('openBCIData', function (data) {
var deltaRange = EEGSpectrumUtils.filterBand(data.spectrums.data, data.spectrums.labels, [0.5, 4]);
$timeout(function () {
$ctrl.labels = deltaRange.labels;
$ctrl.data = deltaRange.spectrums;
});
});
}
});
-10
Ver Arquivo
@@ -1,10 +0,0 @@
<section>
<h2>Frequency</h2>
<canvas id="frequency"
class="chart chart-line"
chart-data="$ctrl.data"
chart-labels="$ctrl.labels"
chart-series="$ctrl.series"
chart-options="{ responsive: true }">
</canvas>
</section>
-16
Ver Arquivo
@@ -1,16 +0,0 @@
angular.module('openbciVisualizer')
.component('bciFrequency', {
templateUrl: 'components/frequency/frequency.html',
controller: function ($timeout) {
var $ctrl = this;
var socket = io();
$ctrl.series = ['Channel 1','Channel 2','Channel 3','Channel 4','Channel 5','Channel 6','Channel 7','Channel 8'];
socket.on('openBCIData', function (data) {
$timeout(function () {
$ctrl.data = data.spectrums.data;
$ctrl.labels = data.spectrums.labels;
});
});
}
});
-9
Ver Arquivo
@@ -1,9 +0,0 @@
<section>
<h2>Theta</h2>
<canvas id="theta"
class="chart chart-bar"
chart-data="$ctrl.data"
chart-labels="$ctrl.series"
chart-series="$ctrl.series">
</canvas>
</section>
-17
Ver Arquivo
@@ -1,17 +0,0 @@
angular.module('openbciVisualizer')
.component('bciTheta', {
templateUrl: 'components/theta/theta.html',
controller: function ($timeout) {
var $ctrl = this;
var socket = io();
$ctrl.series = ['Channel 1','Channel 2','Channel 3','Channel 4','Channel 5','Channel 6','Channel 7','Channel 8'];
socket.on('openBCIData', function (data) {
var thetaRange = EEGSpectrumUtils.filterBand(data.spectrums.data, data.spectrums.labels, [4, 8]);
$timeout(function () {
$ctrl.labels = thetaRange.labels;
$ctrl.data = thetaRange.spectrums;
});
});
}
});
-10
Ver Arquivo
@@ -1,10 +0,0 @@
<section>
<h2>Time</h2>
<canvas id="time"
class="chart chart-line"
chart-data="$ctrl.data"
chart-labels="$ctrl.labels"
chart-series="$ctrl.series"
chart-options="{ responsive: true, scaleOverride: false }">
</canvas>
</section>
-17
Ver Arquivo
@@ -1,17 +0,0 @@
angular.module('openbciVisualizer')
.component('bciTime', {
templateUrl: 'components/time/time.html',
controller: function ($timeout) {
var $ctrl = this;
var socket = io();
$ctrl.series = ['Channel 1','Channel 2','Channel 3','Channel 4','Channel 5','Channel 6','Channel 7','Channel 8'];
socket.on('openBCIData', function (data) {
$timeout(function () {
console.log(data.timeSeries);
$ctrl.labels = data.timeSeries.labels;
$ctrl.data = data.timeSeries.data;
});
});
}
});
-55
Ver Arquivo
@@ -1,55 +0,0 @@
<!doctype html>
<html ng-app="openbciVisualizer">
<head>
<title>OpenBCI Visualizer</title>
<link rel="stylesheet" href="//cdn.jsdelivr.net/angular.chartjs/latest/angular-chart.css">
<script src="node_modules/socket.io-client/socket.io.js"></script>
<script src="/node_modules/angular/angular.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/Chart.js/1.1.1/Chart.js"></script>
<script src="//cdn.jsdelivr.net/angular.chartjs/latest/angular-chart.min.js"></script>
<script src="visualizer.js"></script>
<script src="lib/EEGSpectrumUtils.js"></script>
<script src="components/frequency/frequency.js"></script>
<script src="components/time/time.js"></script>
<script src="components/alpha/alpha.js"></script>
<script src="components/delta/delta.js"></script>
<script src="components/beta/beta.js"></script>
<script src="components/theta/theta.js"></script>
<style>
@import url(https://fonts.googleapis.com/css?family=Roboto:400,700,300);
body {
font-family: 'Roboto', sans-serif;
}
.flex {
display: flex;
}
</style>
</head>
<body>
<main>
<h1>OpenBCI Visualizer</h1>
<bci-time></bci-time>
<bci-frequency></bci-frequency>
<section class="flex">
<bci-alpha></bci-alpha>
<bci-delta></bci-delta>
<bci-beta></bci-beta>
<bci-theta></bci-theta>
</section>
</main>
</body>
</html>
-28
Ver Arquivo
@@ -1,28 +0,0 @@
var EEGSpectrumUtils = {
/**
* filterBand: Give spectrums and labels, it filters the spectrums based on the labels within the range
* @param spectrums
* @param labels
* @param range
* @returns {{spectrums: Array, labels: *}}
*/
filterBand: function (spectrums, labels, range) {
//if (!spectrums ) return console.log();
spectrums = spectrums.map(function (channel) {
return channel.filter(function (spectrum, index) {
return labels[index] >= range[0] && labels[index] <= range[1];
});
});
spectrums = [spectrums.map(function (channel) {
return channel.reduce(function (a, b) {
return a + b;
}) / channel.length;
})];
return {
spectrums: spectrums,
labels: labels
}
}
};
-14
Ver Arquivo
@@ -1,14 +0,0 @@
angular.module('openbciVisualizer', ['chart.js'])
.config(function (ChartJsProvider) {
ChartJsProvider.setOptions({
chartColors: ['#F7464A', '#46BFBD','#FDB45C', '#949FB1','#4D5360', '#803690','#00ADF9', '#FF0000'],
responsive: false,
pointDot: false,
datasetFill: false,
scaleOverride: true,
scaleStartValue: -2,
scaleStepWidth: 1,
scaleSteps: 6
});
});
Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 356 KiB

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 3.6 MiB

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 1.9 MiB

Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 3.6 MiB

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

+3
Ver Arquivo
@@ -0,0 +1,3 @@
export const environment = {
production: false
};
+10
Ver Arquivo
@@ -0,0 +1,10 @@
/* jshint node: true */
module.exports = function(environment) {
return {
environment: environment,
baseURL: '/',
locationType: 'auto'
};
};
+3
Ver Arquivo
@@ -0,0 +1,3 @@
export const environment = {
production: true
};
+51
Ver Arquivo
@@ -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);
+42
Ver Arquivo
@@ -0,0 +1,42 @@
module.exports = function (config) {
config.set({
basePath: '..',
frameworks: ['jasmine'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher')
],
customLaunchers: {
// chrome setup for travis CI using chromium
Chrome_travis_ci: {
base: 'Chrome',
flags: ['--no-sandbox']
}
},
files: [
{ pattern: 'dist/vendor/es6-shim/es6-shim.js', included: true, watched: false },
{ pattern: 'dist/vendor/zone.js/dist/zone.js', included: true, watched: false },
{ pattern: 'dist/vendor/reflect-metadata/Reflect.js', included: true, watched: false },
{ pattern: 'dist/vendor/systemjs/dist/system-polyfills.js', included: true, watched: false },
{ pattern: 'dist/vendor/systemjs/dist/system.src.js', included: true, watched: false },
{ pattern: 'dist/vendor/zone.js/dist/async-test.js', included: true, watched: false },
{ pattern: 'config/karma-test-shim.js', included: true, watched: true },
// Distribution folder.
{ pattern: 'dist/**/*', included: false, watched: true }
],
exclude: [
// Vendor packages might include spec files. We don't want to use those.
'dist/vendor/**/*.spec.js'
],
preprocessors: {},
reporters: ['progress'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false
});
};
+29
Ver Arquivo
@@ -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());
}
};
+14
Ver Arquivo
@@ -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!');
});
});
+9
Ver Arquivo
@@ -0,0 +1,9 @@
export class ClitestPage {
navigateTo() {
return browser.get('/');
}
getParagraphText() {
return element(by.css('clitest-app h1')).getText();
}
}
+17
Ver Arquivo
@@ -0,0 +1,17 @@
{
"compileOnSave": false,
"compilerOptions": {
"declaration": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"mapRoot": "",
"module": "commonjs",
"moduleResolution": "node",
"noEmitOnError": true,
"noImplicitAny": false,
"rootDir": ".",
"sourceMap": true,
"sourceRoot": "/",
"target": "es5"
}
}
+1
Ver Arquivo
@@ -0,0 +1 @@
/// <reference path="../typings/main.d.ts" />
+68 -15
Ver Arquivo
@@ -1,27 +1,80 @@
{
"name": "openbci-visualizer",
"private": false,
"version": "0.0.1",
"name": "openbci-dashboard",
"description": "A fullstack javascript app for capturing and visualizing OpenBCI EEG data",
"main": "visualizer.js",
"version": "0.1.0",
"author": "Alex Castillo <alex@castillo.io>",
"license": "MIT",
"private": false,
"angular-cli": {},
"engines": {
"node": ">= 6.2.0 < 7"
},
"scripts": {
"start": "ng server",
"postinstall": "typings install",
"lint": "tslint \"src/**/*.ts\"",
"format": "clang-format -i -style=file --glob=src/**/*.ts",
"visualize": "concurrently \"ng serve\" \"node src/server/app\" ",
"simulate": "concurrently \"ng serve\" \"node src/server/app simulate\" ",
"pree2e": "webdriver-manager update",
"e2e": "protractor"
},
"keywords": [
"openbci",
"fft",
"chartjs"
"eeg",
"neurojs",
"dashboard"
],
"author": "Alex Castillo",
"scripts": {},
"repository": {
"type": "git",
"url": "git@github.com:NeuroJS/openbci-dashboard.git"
},
"bugs": {
"url": "https://github.com/NeuroJS/openbci-dashboard/issues"
},
"dependencies": {
"angular": "^1.5.3",
"angular-chartjs": "0.0.5",
"chart.js": "^2.0.0",
"dsp.js": "neurojs/dsp.js",
"@angular/common": "2.0.0-rc.1",
"@angular/compiler": "2.0.0-rc.1",
"@angular/core": "2.0.0-rc.1",
"@angular/platform-browser": "2.0.0-rc.1",
"@angular/platform-browser-dynamic": "2.0.0-rc.1",
"@angular/router": "2.0.0-rc.1",
"brainbrowser": "^2.3.0",
"chart.js": "^2.1.4",
"chroma-js": "^1.1.1",
"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.2.0",
"socket.io": "^1.4.5",
"socket.io-client": "^1.4.5",
"yargs": "^4.3.2"
"openbci-sdk": "^0.3.4",
"plotly.js": "^1.10.2",
"reflect-metadata": "0.1.3",
"rxjs": "5.0.0-beta.6",
"smoothie": "^1.27.0",
"socket.io": "^1.4.6",
"socket.io-client": "^1.4.6",
"systemjs": "0.19.26",
"topogrid": "^1.0.6",
"yargs": "^4.3.2",
"zone.js": "^0.6.12"
},
"devDependencies": {
"angular-cli": "0.0.*",
"clang-format": "^1.0.35",
"codelyzer": "0.0.14",
"ember-cli-inject-live-reload": "^1.4.0",
"jasmine-core": "^2.4.1",
"jasmine-spec-reporter": "^2.4.0",
"karma": "^0.13.15",
"karma-chrome-launcher": "^0.2.3",
"karma-jasmine": "^0.3.8",
"protractor": "^3.3.0",
"ts-node": "^0.5.5",
"tslint": "^3.6.0",
"typescript": "^1.8.10",
"typings": "^0.8.1"
}
}
Ver Arquivo
+143
Ver Arquivo
@@ -0,0 +1,143 @@
@import url(https://fonts.googleapis.com/css?family=Roboto:400,700,300);
* {
box-sizing: border-box;
}
:host {
display: block;
height: 100vh;
margin: 0;
}
main {
display: flex;
}
.content {
width: 100%;
}
h1 {
padding: 20px;
font-weight: 300;
margin: 0;
display: block;
}
.capitalize {
text-transform: capitalize;
}
.loading:after {
content: 'Loading...';
text-transform: uppercase;
position: absolute;
top: 50%;
left: 50%;
margin: -10px 0 0 -40px;
font-size: 12px;
}
.row {
display: flex;
}
.block {
display: block;
margin: 20px;
padding: 20px;
box-shadow: 0 0 5px rgba(0,0,0,0.3);
background-color: #333333;
position: relative;
height: 100%;
overflow: hidden;
}
.block h2 {
position: absolute;
margin: 0;
top: 10px;
right: 20px;
font-weight: 300;
}
.block-25 {
width: 25%;
min-height: 200px;
}
.block-33 {
width: 33%;
min-height: 260px;
}
.block-50 {
width: 50%;
min-height: 400px;
}
.block-75 {
width: 75%;
min-height: 580px;
}
.block-100 {
width: 100%;
min-height: 580px;
}
nav {
margin-left: 20px;
margin-bottom: 20px;
}
nav a {
appearance: none;
-webkit-appearance: none;
border: 0;
background: transparent;
color: #ffffff;
padding: 8px 20px;
text-transform: uppercase;
font-family: 'Roboto', sans-serif;
font-weight: 300;
font-size: 12px;
letter-spacing: 1px;
margin-right: 2px;
cursor: pointer;
outline: none;
text-decoration: none;
display: inline-block;
}
nav a:hover {
opacity: 0.8;
}
nav a:nth-child(1) {
background-color: rgba(112,185,252,1);
}
nav a:nth-child(2) {
background-color: rgba(138,219,229,1);
}
nav a:nth-child(3) {
background-color: rgba(162,86,178,1);
}
nav a:nth-child(4) {
background-color: rgba(144,132,246,1);
}
nav a:nth-child(5) {
background-color: rgba(232,223,133,1);
color: #000000;
}
nav a:nth-child(6) {
background-color: rgba(148,159,177,1);
}
+15
Ver Arquivo
@@ -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>
+22
Ver Arquivo
@@ -0,0 +1,22 @@
import {
beforeEachProviders,
describe,
expect,
it,
inject
} from '@angular/core/testing';
import { DashboardComponent } from '../app/dashboard.component';
beforeEachProviders(() => [DashboardComponent]);
describe('App: DashboardComponent', () => {
it('should create the app',
inject([DashboardComponent], (app: DashboardComponent) => {
expect(app).toBeTruthy();
}));
it('should have as title \'clitest works!\'',
inject([DashboardComponent], (app: DashboardComponent) => {
expect(app.title).toEqual('clitest works!');
}));
});
+38
Ver Arquivo
@@ -0,0 +1,38 @@
import { Component, OnInit } from '@angular/core';
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 { Routes, Router, ROUTER_PROVIDERS, ROUTER_DIRECTIVES } from '@angular/router';
@Component({
moduleId: module.id,
selector: 'bci-dashboard',
templateUrl: 'dashboard.component.html',
styleUrls: ['dashboard.component.css'],
directives: [ROUTER_DIRECTIVES, FiltersComponent],
providers: [ROUTER_PROVIDERS]
})
@Routes([
{ path: '/', component: TimeSeriesComponent },
{ 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 class DashboardComponent implements OnInit {
title = 'BCI Dashboard';
constructor (private router: Router) {
}
ngOnInit () {
}
}
+7
Ver Arquivo
@@ -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
};
+66
Ver Arquivo
@@ -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;
}
+20
Ver Arquivo
@@ -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>
+46
Ver Arquivo
@@ -0,0 +1,46 @@
import {
beforeEach,
beforeEachProviders,
describe,
expect,
it,
inject,
} from '@angular/core/testing';
import { ComponentFixture, TestComponentBuilder } from '@angular/compiler/testing';
import { Component } from '@angular/core';
import { By } from '@angular/platform-browser';
import { FiltersComponent } from './filters.component';
describe('Component: Filters', () => {
let builder: TestComponentBuilder;
beforeEachProviders(() => [FiltersComponent]);
beforeEach(inject([TestComponentBuilder], function (tcb: TestComponentBuilder) {
builder = tcb;
}));
it('should inject the component', inject([FiltersComponent],
(component: FiltersComponent) => {
expect(component).toBeTruthy();
}));
it('should create the component', inject([], () => {
return builder.createAsync(FiltersComponentTestController)
.then((fixture: ComponentFixture<any>) => {
let query = fixture.debugElement.query(By.directive(FiltersComponent));
expect(query).toBeTruthy();
expect(query.componentInstance).toBeTruthy();
});
}));
});
@Component({
selector: 'test',
template: `
<bci-filters></bci-filters>
`,
directives: [FiltersComponent]
})
class FiltersComponentTestController {
}
+26
Ver Arquivo
@@ -0,0 +1,26 @@
import { Component, OnInit } from '@angular/core';
import * as io from 'socket.io-client';
import { Constants } from '../shared/constants';
@Component({
moduleId: module.id,
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)
}
}
+1
Ver Arquivo
@@ -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 {
beforeEach,
beforeEachProviders,
describe,
expect,
it,
inject,
} from '@angular/core/testing';
import { ComponentFixture, TestComponentBuilder } from '@angular/compiler/testing';
import { Component } from '@angular/core';
import { By } from '@angular/platform-browser';
import { FrequencyBandComponent } from './frequency-band.component';
describe('Component: FrequencyBand', () => {
let builder: TestComponentBuilder;
beforeEachProviders(() => [FrequencyBandComponent]);
beforeEach(inject([TestComponentBuilder], function (tcb: TestComponentBuilder) {
builder = tcb;
}));
it('should inject the component', inject([FrequencyBandComponent],
(component: FrequencyBandComponent) => {
expect(component).toBeTruthy();
}));
it('should create the component', inject([], () => {
return builder.createAsync(FrequencyBandComponentTestController)
.then((fixture: ComponentFixture<any>) => {
let query = fixture.debugElement.query(By.directive(FrequencyBandComponent));
expect(query).toBeTruthy();
expect(query.componentInstance).toBeTruthy();
});
}));
});
@Component({
selector: 'test',
template: `
<bci-frequency-band></bci-frequency-band>
`,
directives: [FrequencyBandComponent]
})
class FrequencyBandComponentTestController {
}
@@ -0,0 +1,48 @@
import { Component, OnInit, OnDestroy, Input } from '@angular/core';
import * as io from 'socket.io-client';
import { ChartService } from '../shared';
import { CHART_DIRECTIVES } from '../shared/ng2-charts';
import { Constants } from '../shared/constants';
@Component({
moduleId: module.id,
selector: 'bci-frequency-band',
templateUrl: 'frequency-band.component.html',
styleUrls: ['frequency-band.component.css'],
directives: [CHART_DIRECTIVES],
providers: [ChartService, Constants]
})
export class FrequencyBandComponent implements OnInit {
socket: any;
constructor(private chartService: ChartService, private constants: Constants) {
this.socket = io(constants.socket.url);
}
@Input() public type:string;
@Input() public band:string;
@Input() public color:number;
private data:Array<any> = [{ 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);
}
}
+1
Ver Arquivo
@@ -0,0 +1 @@
export { FrequencyBandComponent } from './frequency-band.component';
@@ -0,0 +1,9 @@
<section>
<bci-frequency-band type="bar" band="delta" [color]="1"></bci-frequency-band>
<bci-frequency-band type="bar" band="theta" [color]="2"></bci-frequency-band>
</section>
<section>
<bci-frequency-band type="bar" band="alpha" [color]="3"></bci-frequency-band>
<bci-frequency-band type="bar" band="beta" [color]="4"></bci-frequency-band>
<bci-frequency-band type="bar" band="gamma" [color]="5"></bci-frequency-band>
</section>
@@ -0,0 +1,46 @@
import {
beforeEach,
beforeEachProviders,
describe,
expect,
it,
inject,
} from '@angular/core/testing';
import { ComponentFixture, TestComponentBuilder } from '@angular/compiler/testing';
import { Component } from '@angular/core';
import { By } from '@angular/platform-browser';
import { FrequencyBandsComponent } from './frequency-bands.component';
describe('Component: FrequencyBands', () => {
let builder: TestComponentBuilder;
beforeEachProviders(() => [FrequencyBandsComponent]);
beforeEach(inject([TestComponentBuilder], function (tcb: TestComponentBuilder) {
builder = tcb;
}));
it('should inject the component', inject([FrequencyBandsComponent],
(component: FrequencyBandsComponent) => {
expect(component).toBeTruthy();
}));
it('should create the component', inject([], () => {
return builder.createAsync(FrequencyBandsComponentTestController)
.then((fixture: ComponentFixture<any>) => {
let query = fixture.debugElement.query(By.directive(FrequencyBandsComponent));
expect(query).toBeTruthy();
expect(query.componentInstance).toBeTruthy();
});
}));
});
@Component({
selector: 'test',
template: `
<bci-frequency-bands></bci-frequency-bands>
`,
directives: [FrequencyBandsComponent]
})
class FrequencyBandsComponentTestController {
}
@@ -0,0 +1,19 @@
import { Component, OnInit } from '@angular/core';
import { FrequencyBandComponent } from '../frequency-band';
@Component({
moduleId: module.id,
selector: 'bci-frequency-bands',
templateUrl: 'frequency-bands.component.html',
styleUrls: ['frequency-bands.component.css'],
directives: [FrequencyBandComponent]
})
export class FrequencyBandsComponent implements OnInit {
constructor() {}
ngOnInit() {
}
}
+1
Ver Arquivo
@@ -0,0 +1 @@
export { FrequencyBandsComponent } from './frequency-bands.component';
+30
Ver Arquivo
@@ -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;
}
+11
Ver Arquivo
@@ -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,46 @@
import {
beforeEach,
beforeEachProviders,
describe,
expect,
it,
inject,
} from '@angular/core/testing';
import { ComponentFixture, TestComponentBuilder } from '@angular/compiler/testing';
import { Component } from '@angular/core';
import { By } from '@angular/platform-browser';
import { FrequencyComponent } from './frequency.component';
describe('Component: Frequency', () => {
let builder: TestComponentBuilder;
beforeEachProviders(() => [FrequencyComponent]);
beforeEach(inject([TestComponentBuilder], function (tcb: TestComponentBuilder) {
builder = tcb;
}));
it('should inject the component', inject([FrequencyComponent],
(component: FrequencyComponent) => {
expect(component).toBeTruthy();
}));
it('should create the component', inject([], () => {
return builder.createAsync(FrequencyComponentTestController)
.then((fixture: ComponentFixture<any>) => {
let query = fixture.debugElement.query(By.directive(FrequencyComponent));
expect(query).toBeTruthy();
expect(query.componentInstance).toBeTruthy();
});
}));
});
@Component({
selector: 'test',
template: `
<bci-frequency></bci-frequency>
`,
directives: [FrequencyComponent]
})
class FrequencyComponentTestController {
}
+66
Ver Arquivo
@@ -0,0 +1,66 @@
import { Component, ElementRef, OnInit, OnDestroy, Input } from '@angular/core';
import { RouteSegment, ROUTER_PROVIDERS } from '@angular/router';
import * as io from 'socket.io-client';
import { ChartService } from '../shared';
import { CHART_DIRECTIVES } from '../shared/ng2-charts';
import { Constants } from '../shared/constants';
@Component({
moduleId: module.id,
selector: 'bci-frequency',
templateUrl: 'frequency.component.html',
styleUrls: ['frequency.component.css'],
directives: [CHART_DIRECTIVES],
providers: [ChartService, Constants, ROUTER_PROVIDERS]
})
export class FrequencyComponent implements OnInit {
socket: any;
constructor(private chartService: ChartService,
private segment: RouteSegment,
private constants: Constants) {
this.socket = io(constants.socket.url);
this.type = segment.getParam('type') || 'line';
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);
}
}
+1
Ver Arquivo
@@ -0,0 +1 @@
export { FrequencyComponent } from './frequency.component';
Ver Arquivo
+2
Ver Arquivo
@@ -0,0 +1,2 @@
export {environment} from './environment';
export {DashboardComponent} from './dashboard.component';
+1
Ver Arquivo
@@ -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.
+25
Ver Arquivo
@@ -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;
}
+4
Ver Arquivo
@@ -0,0 +1,4 @@
<section class="motion" [ngClass]="{ 'loading': !model }">
<h2>Motion</h2>
<article id="brainbrowser"></article>
</section>
+46
Ver Arquivo
@@ -0,0 +1,46 @@
import {
beforeEach,
beforeEachProviders,
describe,
expect,
it,
inject,
} from '@angular/core/testing';
import { ComponentFixture, TestComponentBuilder } from '@angular/compiler/testing';
import { Component } from '@angular/core';
import { By } from '@angular/platform-browser';
import { MotionComponent } from './motion.component';
describe('Component: Motion', () => {
let builder: TestComponentBuilder;
beforeEachProviders(() => [MotionComponent]);
beforeEach(inject([TestComponentBuilder], function (tcb: TestComponentBuilder) {
builder = tcb;
}));
it('should inject the component', inject([MotionComponent],
(component: MotionComponent) => {
expect(component).toBeTruthy();
}));
it('should create the component', inject([], () => {
return builder.createAsync(MotionComponentTestController)
.then((fixture: ComponentFixture<any>) => {
let query = fixture.debugElement.query(By.directive(MotionComponent));
expect(query).toBeTruthy();
expect(query.componentInstance).toBeTruthy();
});
}));
});
@Component({
selector: 'test',
template: `
<bci-motion></bci-motion>
`,
directives: [MotionComponent]
})
class MotionComponentTestController {
}
+53
Ver Arquivo
@@ -0,0 +1,53 @@
import { Component, OnInit, ElementRef } from '@angular/core';
import * as io from 'socket.io-client';
import { Constants } from '../shared/constants';
declare var BrainBrowser;
@Component({
moduleId: module.id,
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;
});
});
}
}
+17
Ver Arquivo
@@ -0,0 +1,17 @@
import {
beforeEachProviders,
it,
describe,
expect,
inject
} from '@angular/core/testing';
import { ChartService } from './chart.service';
describe('Chart Service', () => {
beforeEachProviders(() => [ChartService]);
it('should ...',
inject([ChartService], (service: ChartService) => {
expect(service).toBeTruthy();
}));
});
+172
Ver Arquivo
@@ -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: 5,
min: -2,
stepSize: 0.5
}
}],
yAxes: [{
gridLines: {
display: true
},
ticks: {
max: 5,
min: -2,
stepSize: 0.5
}
}]
}
}, this.getChartJSGlobalDefaults());
}
getChartJSBarDefaults (overrides: any = {}): any {
return Object.assign({
scales: {
xAxes: [{
gridLines: {
display: false
},
ticks: {
max: 2,
min: 0,
stepSize: 0.5
}
}],
yAxes: [{
gridLines: {
display: false
},
ticks: {
max: 2,
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);
}
}
+192
Ver Arquivo
@@ -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'
}
]
}
];
}
+2
Ver Arquivo
@@ -0,0 +1,2 @@
export * from './chart.service';
export * from './ng2-charts';
+288
Ver Arquivo
@@ -0,0 +1,288 @@
import {
Component, OnDestroy, OnInit, OnChanges, EventEmitter, ElementRef, Input,
Output
} from '@angular/core';
import {CORE_DIRECTIVES, FORM_DIRECTIVES, NgClass} from '@angular/common';
declare var Chart:any;
@Component({
selector: 'base-chart',
template: `<canvas style="width: 100%; height: 100%;"></canvas>`,
directives: [CORE_DIRECTIVES, FORM_DIRECTIVES, NgClass]
})
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;
@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];
+1
Ver Arquivo
@@ -0,0 +1 @@
export { TimeSeriesComponent } from './time-series.component';
@@ -0,0 +1,82 @@
:host {
display: block;
margin: 20px;
padding: 20px;
box-shadow: 0 0 5px rgba(0,0,0,0.3);
background-color: #333333;
position: relative;
height: 100%;
width: 1060px;
overflow: hidden;
}
h2 {
position: absolute;
margin: 0;
top: 10px;
right: 20px;
font-weight: 300;
}
.time-series canvas {
margin: 40px 80px 40px 40px;
}
.time-series-channels,
.time-series-amplitudes {
position: absolute;
top: 60px;
height: 450px;
color: rgba(102,102,102,1);
font-size: 12px;
}
.time-series-channels {
width: 40px;
left: 20px;
text-align: left;
color: #000000;
font-weight: 500;
}
.time-series-amplitudes {
right: 20px;
text-align: right;
}
.time-series-channels ul {
width: 100%;
}
.time-series-channels li {
width: 100%;
height: 100%;
padding-top: 20px;
}
.time-series-channels ul,
.time-series-amplitudes ul {
display: flex;
align-items: flex-end;
justify-content: space-around;
flex-direction: column;
height: 100%;
margin: 0;
padding: 0;
list-style: none;
}
.time-series-duration {
position: absolute;
left: 50px;
width: 98%;
bottom: 22px;
display: flex;
color: rgba(102,102,102,1);
font-size: 12px;
}
.time-series-duration time {
width: 20%;
}
@@ -0,0 +1,26 @@
<section class="time-series" [ngClass]="{ 'loading': !amplitudes }">
<h2>Time Series</h2>
<aside class="time-series-channels">
<ul>
<li *ngFor="let channel of channels, let i = index"
[ngStyle]="{ 'color': colors[i].borderColor }">
{{ channel }}
</li>
</ul>
</aside>
<canvas id="timeSeries" width="950" height="450"></canvas>
<aside class="time-series-amplitudes">
<ul>
<li *ngFor="let amplitude of amplitudes, let i = index"
[ngStyle]="{ 'color': colors[i].borderColor }">
{{ amplitude }}
</li>
</ul>
</aside>
<footer class="time-series-duration">
<time *ngFor="let time of timeline"
datetime="P1M">
{{ time }}
</time>
</footer>
</section>
@@ -0,0 +1,46 @@
import {
beforeEach,
beforeEachProviders,
describe,
expect,
it,
inject,
} from '@angular/core/testing';
import { ComponentFixture, TestComponentBuilder } from '@angular/compiler/testing';
import { Component } from '@angular/core';
import { By } from '@angular/platform-browser';
import { TimeSeriesComponent } from './time-series.component';
describe('Component: TimeSeries', () => {
let builder: TestComponentBuilder;
beforeEachProviders(() => [TimeSeriesComponent]);
beforeEach(inject([TestComponentBuilder], function (tcb: TestComponentBuilder) {
builder = tcb;
}));
it('should inject the component', inject([TimeSeriesComponent],
(component: TimeSeriesComponent) => {
expect(component).toBeTruthy();
}));
it('should create the component', inject([], () => {
return builder.createAsync(TimeSeriesComponentTestController)
.then((fixture: ComponentFixture<any>) => {
let query = fixture.debugElement.query(By.directive(TimeSeriesComponent));
expect(query).toBeTruthy();
expect(query.componentInstance).toBeTruthy();
});
}));
});
@Component({
selector: 'test',
template: `
<bci-time-series></bci-time-series>
`,
directives: [TimeSeriesComponent]
})
class TimeSeriesComponentTestController {
}
+68
Ver Arquivo
@@ -0,0 +1,68 @@
import { Component, ElementRef, OnInit, OnDestroy } from '@angular/core';
import { SmoothieChart, TimeSeries } from 'smoothie';
import { ChartService } from '../shared';
import * as io from 'socket.io-client';
import { Constants } from '../shared/constants';
@Component({
moduleId: module.id,
selector: 'bci-time-series',
templateUrl: 'time-series.component.html',
styleUrls: ['time-series.component.css'],
providers: [ChartService, Constants]
})
export class TimeSeriesComponent implements OnInit {
socket: any;
constructor(private view: ElementRef,
private chartService: ChartService,
private constants: Constants) {
this.socket = io(constants.socket.url);
this.chartService = chartService;
}
private options = this.chartService.getChartSmoothieDefaults();
private timeSeries = new SmoothieChart(this.options);
private amplitudes = [];
private timeline = [];
private lines = Array(8).fill(0).map(() => new TimeSeries());
private channels = this.chartService.getChannels();
private colors = this.chartService.getColors();
ngOnInit() {
this.addTimeSeriesLines();
this.socket.on(this.constants.socket.events.time, (data) => {
this.amplitudes = data.amplitudes;
this.timeline = data.timeline;
this.appendTimeSeriesLines(data.data);
});
}
ngOnDestroy () {
this.socket.removeListener(this.constants.socket.events.time);
}
ngAfterViewInit () {
this.timeSeries.streamTo(this.view.nativeElement.querySelector('canvas'), 40);
}
addTimeSeriesLines () {
this.lines.forEach((line, index) => {
this.timeSeries.addTimeSeries(line, {
strokeStyle: this.colors[index].borderColor
});
});
}
appendTimeSeriesLines (data) {
this.lines.forEach((line, index) => {
data[index].forEach((amplitude) => {
line.append(new Date().getTime(), amplitude);
});
});
}
}
+1
Ver Arquivo
@@ -0,0 +1 @@
export { TopoComponent } from './topo.component';
+101
Ver Arquivo
@@ -0,0 +1,101 @@
* {
box-sizing: border-box;
}
:host {
display: block;
margin: 20px;
padding: 20px;
box-shadow: 0 0 5px rgba(0,0,0,0.3);
background-color: #333333;
position: relative;
height: 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'] {
z-index: 2;
background-color: black;
border: 1px solid black;
width: 5%;
height: 5%;
border-radius: 50%;
position: absolute;
}
.top {
top: 2%
}
.middle {
top: calc(50% - 50px);
}
.third {
top: 70%;
}
.bottom {
bottom: 2%;
}
.top.left,
.bottom.left {
left: 32%;
}
.top.right,
.bottom.right {
right: 32%;
}
.middle.left {
left: 25%;
}
.middle.right {
right: 25%;
}
.third.left {
left: 8%;
}
.third.right {
right: 8%;
}
/* Grid */
.topoplot-grid {
position: absolute;
z-index: 1;
width: 100%;
height: 100%;
-webkit-clip-path: circle(50%);
border-radius: 50%;
-webkit-filter: blur(10px);
}
[class*='topoplot-u'] {
float: left;
/*border: 1px solid lightgray;*/
width: 9.09%;
height: 9.09%;
/*background: lightblue;*/
}
+6
Ver Arquivo
@@ -0,0 +1,6 @@
<section [ngClass]="{ loading: !data }">
<h2>Topo</h2>
<section class="topoplot">
<div id="topo"></div>
</section>
</section>
+46
Ver Arquivo
@@ -0,0 +1,46 @@
import {
beforeEach,
beforeEachProviders,
describe,
expect,
it,
inject,
} from '@angular/core/testing';
import { ComponentFixture, TestComponentBuilder } from '@angular/compiler/testing';
import { Component } from '@angular/core';
import { By } from '@angular/platform-browser';
import { TopoComponent } from './topo.component';
describe('Component: Topo', () => {
let builder: TestComponentBuilder;
beforeEachProviders(() => [TopoComponent]);
beforeEach(inject([TestComponentBuilder], function (tcb: TestComponentBuilder) {
builder = tcb;
}));
it('should inject the component', inject([TopoComponent],
(component: TopoComponent) => {
expect(component).toBeTruthy();
}));
it('should create the component', inject([], () => {
return builder.createAsync(TopoComponentTestController)
.then((fixture: ComponentFixture<any>) => {
let query = fixture.debugElement.query(By.directive(TopoComponent));
expect(query).toBeTruthy();
expect(query.componentInstance).toBeTruthy();
});
}));
});
@Component({
selector: 'test',
template: `
<bci-topo></bci-topo>
`,
directives: [TopoComponent]
})
class TopoComponentTestController {
}
+56
Ver Arquivo
@@ -0,0 +1,56 @@
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({
moduleId: module.id,
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();
}
}
BIN
Ver Arquivo
Arquivo binário não exibido.

Depois

Largura:  |  Altura:  |  Tamanho: 5.3 KiB

+51
Ver Arquivo
@@ -0,0 +1,51 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>BCI Dashboard</title>
<base href="/">
{{content-for 'head'}}
<link rel="icon" type="image/x-icon" href="favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
@import url(https://fonts.googleapis.com/css?family=Roboto:400,700,300);
body {
font-family: 'Roboto', sans-serif;
font-weight: 300;
color: #ffffff;
background-color: #222222;
margin: 0;
}
</style>
<!-- Service worker support is disabled by default.
Install the worker script and uncomment to enable.
Only enable service workers in production.
<script type="text/javascript">
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/worker.js').catch(function(err) {
console.log('Error installing service worker: ', err);
});
}
</script>
-->
</head>
<body>
<bci-dashboard>Loading...</bci-dashboard>
<script src="vendor/chart.js/dist/Chart.bundle.js"></script>
<script src="vendor/chroma-js/chroma.js"></script>
<script src="vendor/plotly.js/dist/plotly.js"></script>
<script src="vendor/brainbrowser/build/brainbrowser-2.3.0/brainbrowser.surface-viewer.min.js"></script>
<script src="vendor/es6-shim/es6-shim.js"></script>
<script src="vendor/reflect-metadata/Reflect.js"></script>
<script src="vendor/systemjs/dist/system.src.js"></script>
<script src="vendor/zone.js/dist/zone.js"></script>
<script>
System.import('system-config.js').then(function () {
System.import('main');
}).catch(console.error.bind(console));
</script>
</body>
</html>
+9
Ver Arquivo
@@ -0,0 +1,9 @@
import { bootstrap } from '@angular/platform-browser-dynamic';
import { enableProdMode } from '@angular/core';
import { DashboardComponent, environment } from './app/';
if (environment.production) {
enableProdMode();
}
bootstrap(DashboardComponent);
+28
Ver Arquivo
@@ -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!
*/
}
+9
Ver Arquivo
@@ -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);
}
}
+58
Ver Arquivo
@@ -0,0 +1,58 @@
'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: {
bufferSize: 512,
windowFunction: 'none'
},
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
}
};
+84
Ver Arquivo
@@ -0,0 +1,84 @@
'use strict';
const Fili = require('fili');
const Utils = require('../utils');
const constants = require('../constants');
module.exports = class FFT {
constructor ({ Signal }) {
this.signal = Signal;
this.bufferSize = constants.fft.bufferSize;
this.bins = this.bufferSize / 4;
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) => {
//signals = this.trim(signals);
this.signalsToFFT(signals);
this.scaleLabels();
this.filterBands();
this.filterLabels();
this.emit();
});
}
trim (signals) {
return signals.map((channel) => {
return channel.splice(this.bins, this.bufferSize);
});
}
signalsToFFT (signals) {
signals.forEach((signal, index) => {
//signal = Utils.filter.process(signal);
let fft = new Fili.Fft(constants.fft.bufferSize);
let spectrum = fft.forward(signal, constants.fft.windowFunction);
this.spectrums[index] = fft.magnitude(spectrum);
this.spectrums[index] = Utils.signal.voltsToMicrovolts(this.spectrums[index], true);
console.log('this.spectrums[index]', this.spectrums[index].length);
});
}
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
});
}
}
+11
Ver Arquivo
@@ -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
}
+67
Ver Arquivo
@@ -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
});
}
}

Alguns arquivos não foram exibidos porque demasiados arquivos foram alterados neste diff Mostrar Mais