Added chart components
Esse commit está contido em:
externo
BIN
Arquivo binário não exibido.
@@ -7,3 +7,5 @@ public
|
||||
coverage
|
||||
.DS_Store
|
||||
dist
|
||||
|
||||
.DS_Store
|
||||
|
||||
+23
-2
@@ -1,11 +1,32 @@
|
||||
|
||||
angular.module('OpenEXP', [
|
||||
'ui.router'
|
||||
'ui.router',
|
||||
'chart.js'
|
||||
])
|
||||
.config(($stateProvider, $urlRouterProvider) => {
|
||||
.config(($stateProvider, $urlRouterProvider, ChartJsProvider) => {
|
||||
$urlRouterProvider.otherwise('/');
|
||||
|
||||
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,
|
||||
barShowStroke: true,
|
||||
barStrokeWidth: 1,
|
||||
strokeColor: 'rgba(116,150,161,1)'
|
||||
});
|
||||
|
||||
})
|
||||
|
||||
.run(($state) => {
|
||||
|
||||
});
|
||||
|
||||
@@ -7,3 +7,72 @@
|
||||
@import '../components/navbar/navbar.scss';
|
||||
@import './connect/connect.scss';
|
||||
@import '../app/login/login.scss';
|
||||
|
||||
|
||||
@import url(https://fonts.googleapis.com/css?family=Roboto:400,700,300);
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
color: #ffffff;
|
||||
background-color: #222222;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 20px;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.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: auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.block h2 {
|
||||
position: absolute;
|
||||
margin: 0;
|
||||
top: 10px;
|
||||
right: 20px;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.block-25 {
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
.block-33 {
|
||||
width: 33%;
|
||||
}
|
||||
|
||||
.block-50 {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.block-100 {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.time-series-duration {
|
||||
position: absolute;
|
||||
width: 110%;
|
||||
bottom: 30px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.time-series-duration time {
|
||||
width: 20%;
|
||||
}
|
||||
@@ -1,9 +1,119 @@
|
||||
var dsp = require('dsp.js');
|
||||
var topogrid = require('topogrid');
|
||||
|
||||
angular.module('OpenEXP')
|
||||
.controller('DashboardCtrl', ['$scope', 'boardFactory', ($scope, boardFactory) => {
|
||||
.controller('DashboardCtrl', ['$scope', '$timeout', 'boardFactory', ($scope, $timeout, boardFactory) => {
|
||||
|
||||
$scope.board = boardFactory.board;
|
||||
$scope.board = boardFactory.board;
|
||||
|
||||
$scope.publish = boardFactory.publish;
|
||||
$scope.unpublish = boardFactory.unpublish;
|
||||
$scope.publish = boardFactory.publish;
|
||||
$scope.unpublish = boardFactory.unpublish;
|
||||
|
||||
}]);
|
||||
var bins = 128; // Approx .5 second
|
||||
var bufferSize = 128;
|
||||
var windowRefreshRate = 8;
|
||||
var windowSize = bins / windowRefreshRate;
|
||||
var sampleRate = 250;
|
||||
var sampleNumber = 0;
|
||||
var signals = [[], [], [], [], [], [], [], []];
|
||||
|
||||
var timeSeriesWindow = 5; // in seconds
|
||||
var timeSeriesRate = 25; // skips every 10 samples
|
||||
var seriesNumber = 0;
|
||||
var timeSeries = new Array(8).fill([]); // 8 channels
|
||||
|
||||
// the parameters for the grid [x,y,z] where x is the min of the grid, y is the
|
||||
// max of the grid and z is the number of points
|
||||
var grid_params = [0, 10, 11];
|
||||
var pos_x = [3, 7, 2, 8, 0, 10, 3, 7]; // x coordinates of the data
|
||||
var pos_y = [0, 0, 3, 3, 8, 8, 10, 10]; // y coordinates of the data
|
||||
// var data = [10,0,0,0,0,0,-10,30,25]; // the data values
|
||||
|
||||
timeSeries = timeSeries.map(function (channel) {
|
||||
return new Array((sampleRate * timeSeriesWindow) / timeSeriesRate).fill(0)
|
||||
});
|
||||
|
||||
boardFactory.board.on('sample', function (sample) {
|
||||
//console.log('sample', sample);
|
||||
sampleNumber++;
|
||||
|
||||
Object.keys(sample.channelData).forEach(function (channel, i) {
|
||||
signals[i].push(sample.channelData[channel]);
|
||||
});
|
||||
|
||||
if (sampleNumber === bins) {
|
||||
|
||||
var spectrums = [[], [], [], [], [], [], [], []];
|
||||
|
||||
signals.forEach(function (signal, index) {
|
||||
var fft = new dsp.FFT(bufferSize, sampleRate);
|
||||
fft.forward(signal);
|
||||
spectrums[index] = parseObjectAsArray(fft.spectrum);
|
||||
spectrums[index] = voltsToMicrovolts(spectrums[index], true);
|
||||
});
|
||||
|
||||
var scaler = sampleRate / bins;
|
||||
|
||||
var labels = new Array(bins / 2).fill()
|
||||
.map(function (x, i) {
|
||||
return Math.ceil(i * scaler);
|
||||
});
|
||||
|
||||
var grid = topogrid.create(pos_x, pos_y, sample.channelData, grid_params);
|
||||
|
||||
$timeout(function () {
|
||||
$scope.frequencyData = spectrums;
|
||||
$scope.frequencyLabels = labels.map(function (label, i) {
|
||||
return i % 10 === 0 ? label : '';
|
||||
});
|
||||
$scope.gridData = [].concat.apply([], grid);
|
||||
});
|
||||
|
||||
signals = signals.map(function (channel) {
|
||||
return channel.filter(function (signal, index) {
|
||||
return index > (windowSize - 1);
|
||||
});
|
||||
});
|
||||
|
||||
sampleNumber = bins - windowSize;
|
||||
|
||||
}
|
||||
|
||||
seriesNumber++;
|
||||
|
||||
// Time Series
|
||||
if (seriesNumber === timeSeriesRate) {
|
||||
|
||||
timeSeries.forEach(function (channel, index) {
|
||||
channel.push(voltsToMicrovolts(sample.channelData[index]));
|
||||
channel.shift();
|
||||
});
|
||||
|
||||
$timeout(function () {
|
||||
$scope.timeData = timeSeries;
|
||||
$scope.timeLabels = new Array((sampleRate * timeSeriesWindow) / timeSeriesRate).fill(0)
|
||||
});
|
||||
|
||||
|
||||
seriesNumber = 0;
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
|
||||
function voltsToMicrovolts(volts, log) {
|
||||
if (!Array.isArray(volts)) volts = [volts];
|
||||
return volts.map(function (volt) {
|
||||
return log ? Math.log10(Math.pow(10, 6) * volt) : Math.pow(10, 6) * volt;
|
||||
});
|
||||
}
|
||||
|
||||
function parseObjectAsArray(obj) {
|
||||
var array = [];
|
||||
Object.keys(obj).forEach(function (key) {
|
||||
array.push(obj[key]);
|
||||
});
|
||||
return array;
|
||||
}
|
||||
|
||||
}]);
|
||||
|
||||
@@ -2,3 +2,13 @@
|
||||
<button class="btn btn-lg btn-info" ng-click="publish()"><span class="glyphicon glyphicon-play"></button>
|
||||
<button class="btn btn-lg btn-warning" ng-click="unpublish()"><span class="glyphicon glyphicon-pause"></button>
|
||||
</center>
|
||||
|
||||
<section class="row">
|
||||
<bci-frequency ng-if="frequencyData" data="frequencyData" labels="frequencyLabels" class="block block-50"></bci-frequency>
|
||||
<bci-topo ng-if="gridData" data="gridData" class="block block-50"></bci-topo>
|
||||
</section>
|
||||
|
||||
<section class="row">
|
||||
<bci-time ng-if="timeData" data="timeData" labels="timeLabels" class="block block-100"></bci-time>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -7,3 +7,4 @@ angular.module('OpenEXP')
|
||||
templateUrl: './app/dashboard/dashboard.html'
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ angular.module('OpenEXP')
|
||||
// each time there is a change of type add, log it
|
||||
var observer = (changes) => {
|
||||
changes.forEach(change => {
|
||||
if(change.type === "add") console.log(change)
|
||||
//if(change.type === "add") console.log(change)
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<section>
|
||||
<h2>Frequency</h2>
|
||||
<canvas id="frequency"
|
||||
ng-if="$ctrl.data"
|
||||
class="chart chart-line"
|
||||
chart-data="$ctrl.data"
|
||||
chart-labels="$ctrl.labels"
|
||||
chart-series="$ctrl.series"
|
||||
chart-options="$ctrl.options"
|
||||
chart-colours="$ctrl.colors">
|
||||
</canvas>
|
||||
</section>
|
||||
@@ -0,0 +1,46 @@
|
||||
angular.module('OpenEXP')
|
||||
.directive('bciFrequency', function () {
|
||||
return {
|
||||
restrict: 'E',
|
||||
scope: {
|
||||
data: '=',
|
||||
labels: '='
|
||||
},
|
||||
templateUrl: './components/frequency/frequency.html',
|
||||
controllerAs: '$ctrl',
|
||||
bindToController: true,
|
||||
controller: function () {
|
||||
|
||||
var $ctrl = this;
|
||||
|
||||
$ctrl.options = {
|
||||
responsive: true,
|
||||
animation: true,
|
||||
animationSteps: 5
|
||||
};
|
||||
|
||||
$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.series = [
|
||||
'Channel 1',
|
||||
'Channel 2',
|
||||
'Channel 3',
|
||||
'Channel 4',
|
||||
'Channel 5',
|
||||
'Channel 6',
|
||||
'Channel 7',
|
||||
'Channel 8'
|
||||
];
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
<section class="time-series">
|
||||
<h2>Time Series</h2>
|
||||
<canvas ng-repeat="channel in $ctrl.data track by $index"
|
||||
ng-style="{ position: 'absolute', top: ((60 * $index)) + 'px', padding: '20px 0' }"
|
||||
height="50"
|
||||
id="channel1"
|
||||
ng-if="$ctrl.data"
|
||||
class="chart chart-line"
|
||||
chart-data="[channel]"
|
||||
chart-labels="$ctrl.labels"
|
||||
chart-options="$ctrl.options"
|
||||
chart-colours="[$ctrl.colors[$index]]">
|
||||
</canvas>
|
||||
<footer class="time-series-duration">
|
||||
<time ng-repeat="second in [5,4,3,2,1,0] track by $index" datetime="P1M">
|
||||
<span ng-if="second">-</span>{{ second }}
|
||||
</time>
|
||||
</footer>
|
||||
<div style="height: 600px"></div>
|
||||
</section>
|
||||
@@ -0,0 +1,50 @@
|
||||
angular.module('OpenEXP')
|
||||
.directive('bciTime', function () {
|
||||
return {
|
||||
restrict: 'E',
|
||||
scope: {
|
||||
data: '=',
|
||||
labels: '='
|
||||
},
|
||||
templateUrl: './components/time/time.html',
|
||||
controllerAs: '$ctrl',
|
||||
bindToController: true,
|
||||
controller: function () {
|
||||
|
||||
var $ctrl = this;
|
||||
|
||||
$ctrl.options = {
|
||||
animation: false,
|
||||
responsive: true,
|
||||
showScale: false,
|
||||
scaleOverride: true,
|
||||
scaleStartValue: -500,
|
||||
scaleStepWidth: 1,
|
||||
scaleSteps: 1500
|
||||
};
|
||||
|
||||
$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.series = [
|
||||
'Channel 1',
|
||||
'Channel 2',
|
||||
'Channel 3',
|
||||
'Channel 4',
|
||||
'Channel 5',
|
||||
'Channel 6',
|
||||
'Channel 7',
|
||||
'Channel 8'
|
||||
];
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,82 @@
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.topoplot-wrapper {
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
border: 3px solid black;
|
||||
border-radius: 50%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
[class*='topoplot-c'] {
|
||||
z-index: 2;
|
||||
background-color: black;
|
||||
border: 1px solid black;
|
||||
width: 5%;
|
||||
height: 5%;
|
||||
border-radius: 50%;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.top {
|
||||
top: 2%
|
||||
}
|
||||
.middle {
|
||||
top: calc(50% - 50px);
|
||||
}
|
||||
.third {
|
||||
top: 70%;
|
||||
}
|
||||
.bottom {
|
||||
bottom: 2%;
|
||||
}
|
||||
|
||||
.top.left,
|
||||
.bottom.left {
|
||||
left: 32%;
|
||||
}
|
||||
|
||||
.top.right,
|
||||
.bottom.right {
|
||||
right: 32%;
|
||||
}
|
||||
|
||||
.middle.left {
|
||||
left: 25%;
|
||||
}
|
||||
|
||||
.middle.right {
|
||||
right: 25%;
|
||||
}
|
||||
|
||||
.third.left {
|
||||
left: 8%;
|
||||
}
|
||||
|
||||
.third.right {
|
||||
right: 8%;
|
||||
}
|
||||
|
||||
|
||||
/* Grid */
|
||||
|
||||
.topoplot-grid {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
-webkit-clip-path: circle(50%);
|
||||
border-radius: 50%;
|
||||
-webkit-filter: blur(10px);
|
||||
}
|
||||
|
||||
[class*='topoplot-u'] {
|
||||
float: left;
|
||||
/*border: 1px solid lightgray;*/
|
||||
width: 9.09%;
|
||||
height: 9.09%;
|
||||
/*background: lightblue;*/
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<section>
|
||||
<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.data track by $index" ng-style="$ctrl.getColor($index,pixel,$ctrl.data)" ng-class="$ctrl.getClass($index)"></div>
|
||||
</aside>
|
||||
|
||||
</section>
|
||||
</section>
|
||||
@@ -0,0 +1,28 @@
|
||||
angular.module('OpenEXP')
|
||||
.directive('bciTopo', function () {
|
||||
return {
|
||||
restrict: 'E',
|
||||
scope: {
|
||||
data: '='
|
||||
},
|
||||
templateUrl: './components/topo/topo.html',
|
||||
controllerAs: '$ctrl',
|
||||
bindToController: true,
|
||||
controller: function () {
|
||||
|
||||
var $ctrl = this;
|
||||
|
||||
$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)}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -3,6 +3,9 @@
|
||||
<meta charset="UTF-8">
|
||||
<title>OpenEXP</title>
|
||||
|
||||
<link rel="stylesheet" href="http://cdn.jsdelivr.net/angular.chartjs/latest/angular-chart.css">
|
||||
<link rel="stylesheet" href="components/topo/topo.css">
|
||||
|
||||
</head>
|
||||
<body ng-app="OpenEXP">
|
||||
<header id="nav">
|
||||
@@ -14,6 +17,9 @@
|
||||
<!--COMMENT OUT NEXT THREE LINES FOR PRODUCTION-->
|
||||
<script src="http://localhost:8080/webpack-dev-server.js"></script>
|
||||
<script src="http://localhost:8080/build/vendor.js"></script>
|
||||
<script src="lib/chroma.min.js"></script>
|
||||
<script src="http://cdnjs.cloudflare.com/ajax/libs/Chart.js/1.1.1/Chart.js"></script>
|
||||
<script src="http://cdn.jsdelivr.net/angular.chartjs/latest/angular-chart.min.js"></script>
|
||||
<script src="http://localhost:8080/build/app.js"></script>
|
||||
|
||||
<!--UNCOMMENT NEXT TWO LINES FOR PRODUCTION-->
|
||||
|
||||
@@ -5,6 +5,9 @@ module.exports = function() {
|
||||
require('./app/app');
|
||||
|
||||
// FACTORY METHODS
|
||||
require('./components/frequency/frequency.js');
|
||||
require('./components/time/time.js');
|
||||
require('./components/topo/topo.js');
|
||||
require('./components/board/boardFactory.js');
|
||||
|
||||
// PAGES
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
|
||||
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('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
|
||||
}
|
||||
}
|
||||
};
|
||||
externo
+33
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
@@ -6,6 +6,9 @@ module.exports = function() {
|
||||
|
||||
// JS
|
||||
require('script!angular/angular.js');
|
||||
// VERSIONS ARE NO GOOD
|
||||
//require('script!chart.js/Chart.js');
|
||||
//require('script!angular-chartjs/dist/angular-chartjs.js');
|
||||
require('script!angular-ui-router/release/angular-ui-router.js');
|
||||
require('script!bootstrap/dist/js/bootstrap.min.js');
|
||||
require('script!jspsych/jspsych.js');
|
||||
|
||||
@@ -71,15 +71,19 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"angular": "1.5.0-rc.0",
|
||||
"angular-chartjs": "0.0.5",
|
||||
"angular-ui-bootstrap": "^0.14.3",
|
||||
"angular-ui-router": "^0.2.15",
|
||||
"babel": "^6.3.13",
|
||||
"bootstrap": "^3.3.6",
|
||||
"chart.js": "^1.1.1",
|
||||
"dsp.js": "github:neurojs/dsp.js",
|
||||
"gulp": "^3.9.0",
|
||||
"jquery": "^2.1.4",
|
||||
"jspsych": "git://github.com/teonlamont/jsPsych.git#590313c2fc528c147a5d6040f001c7407ee3623b",
|
||||
"ngmin": "^0.5.0",
|
||||
"openbci-sdk": "^0.2.0",
|
||||
"topogrid": "^1.0.6",
|
||||
"url-loader": "^0.5.7"
|
||||
}
|
||||
}
|
||||
|
||||
Referência em uma Nova Issue
Bloquear um usuário