8 Commits

Autor SHA1 Mensagem Data
Alex Castillo c1ad1e0476 ng2-charts fixes
TODO: clean-up, test, optimize and submit as a PR
2016-09-09 15:32:52 +02:00
Alex Castillo 70332ca560 buffer size and scale update 2016-09-09 14:32:01 +02:00
Alex Castillo 12fbbdd06b clean-up 2016-09-09 14:31:29 +02:00
Alex Castillo 723eb8c533 chart scale adjustment 2016-06-13 21:14:54 -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
28 arquivos alterados com 486 adições e 322 exclusões
+1 -1
Ver Arquivo
@@ -7,7 +7,7 @@ module.exports = function(defaults) {
vendorNpmFiles: [
'socket.io-client/socket.io.js',
'smoothie/smoothie.js',
'chart.js/Chart.js',
'chart.js/dist/Chart.bundle.js',
'ng2-charts/bundles/ng2-charts.js',
'chroma-js/chroma.js',
'plotly.js/dist/plotly.js',
+2 -2
Ver Arquivo
@@ -41,14 +41,14 @@
"@angular/platform-browser-dynamic": "2.0.0-rc.1",
"@angular/router": "2.0.0-rc.1",
"brainbrowser": "^2.3.0",
"chart.js": "^1.0.2",
"chart.js": "^2.1.4",
"chroma-js": "^1.1.1",
"dsp.js": "neurojs/dsp.js",
"es6-shim": "^0.35.0",
"express": "^4.13.4",
"fili": "^1.2.1",
"jstat": "^1.5.2",
"ng2-charts": "^1.0.3",
"ng2-charts": "^1.1.0",
"nodemon": "^1.9.1",
"openbci-sdk": "^0.3.4",
"plotly.js": "^1.10.2",
+2 -2
Ver Arquivo
@@ -4,8 +4,8 @@
<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/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>
+6 -2
Ver Arquivo
@@ -1,6 +1,6 @@
import { Component, OnInit } from '@angular/core';
import * as io from 'socket.io-client';
import { Constants } from '../shared/constants';
import { Constants } from '../shared';
@Component({
moduleId: module.id,
@@ -13,6 +13,7 @@ import { Constants } from '../shared/constants';
export class FiltersComponent {
socket: any;
constructor(private constants: Constants) {
this.socket = io(this.constants.socket.url);
}
@@ -20,7 +21,10 @@ export class FiltersComponent {
private filters: Array<any> = this.constants.filters;
applyFilter (filter) {
this.socket.emit(this.constants.socket.events.filter, filter)
this.socket.emit(
this.constants.socket.events.filter,
filter
);
}
}
@@ -1,4 +1,10 @@
.chart {
display: block;
width: 100%;
height: 100%;
}
:host {
display: inline-block;
margin: 0 0 20px 20px;
@@ -22,4 +28,9 @@ h2 {
.capitalize {
text-transform: capitalize;
}
.frequency-band {
height: 100%;
padding-top: 40px;
}
@@ -1,10 +1,11 @@
<section class="frequency-band" [ngClass]="{ 'loading': !data }">
<h2 class="capitalize">{{ band }} {{ type }}</h2>
<base-chart class="chart"
[data]="data"
[datasets]="data"
[labels]="channels"
[options]="options"
[colours]="colors"
[colors]="colors"
[legend]="false"
[series]="channels"
[chartType]="type">
</base-chart>
+19 -13
Ver Arquivo
@@ -1,8 +1,7 @@
import { Component, OnInit, OnDestroy, Input } from '@angular/core';
import * as io from 'socket.io-client';
import { ChartService } from '../shared';
import { ChartService, Constants } from '../shared';
import { CHART_DIRECTIVES } from '../shared/ng2-charts';
import { Constants } from '../shared/constants';
@Component({
moduleId: module.id,
@@ -13,10 +12,12 @@ import { Constants } from '../shared/constants';
providers: [ChartService, Constants]
})
export class FrequencyBandComponent implements OnInit {
export class FrequencyBandComponent implements OnInit, OnDestroy {
socket: any;
constructor(private chartService: ChartService, private constants: Constants) {
constructor(private chartService: ChartService,
private constants: Constants) {
this.socket = io(constants.socket.url);
}
@@ -24,22 +25,27 @@ export class FrequencyBandComponent implements OnInit {
@Input() public band:string;
@Input() public color:number;
private data:Array<any> = [[]];
private colors:Array<any>;
private channels:Array<string> = this.chartService.getChannels();
private options:any = this.chartService.getChartJSDefaults({
responsive: false
});
private data:Array<any> = [{ data: [], label: [] }];
private colors = this.chartService.getColorByIndex(this.color);
private channels = this.chartService.getChannels();
private options = this.chartService.getChartJSBarDefaults();
ngOnInit() {
ngOnInit() {
this.colors = this.chartService.getColorByIndex(this.color);
this.socket.on(this.constants.socket.events.fft, (data) => {
this.data = data[this.band || 'data'];
this.data = [];
data[this.band || 'data'].forEach((dataset, index) => {
this.data.push({
data: dataset
});
});
});
}
ngOnDestroy () {
this.socket.removeListener(this.constants.socket.events.fft);
this.socket.removeListener(
this.constants.socket.events.fft
);
}
}
@@ -1,9 +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>
<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>
<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>
+8 -1
Ver Arquivo
@@ -1,6 +1,8 @@
.chart {
display: block;
width: 100%;
height: 100%;
}
:host {
@@ -10,7 +12,7 @@
box-shadow: 0 0 5px rgba(0,0,0,0.3);
background-color: #333333;
position: relative;
height: 100%;
height: 600px;
overflow: hidden;
}
@@ -20,4 +22,9 @@ h2 {
top: 10px;
right: 20px;
font-weight: 300;
}
.frequency {
height: 100%;
padding-top: 40px;
}
+2 -2
Ver Arquivo
@@ -1,10 +1,10 @@
<section class="frequency" [ngClass]="{ 'loading': !data }">
<h2>Frequency {{ type }}</h2>
<base-chart class="chart"
[data]="data"
[datasets]="data"
[labels]="labels"
[options]="options"
[colours]="colors"
[colors]="colors"
[series]="channels"
[chartType]="type">
</base-chart>
+29 -7
Ver Arquivo
@@ -1,9 +1,8 @@
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 { ChartService, Constants } from '../shared';
import { CHART_DIRECTIVES } from '../shared/ng2-charts';
import { Constants } from '../shared/constants';
@Component({
moduleId: module.id,
@@ -17,30 +16,53 @@ import { Constants } from '../shared/constants';
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.type = segment.getParam('type') || 'line';
this.setOptions(this.type);
}
@Input() type:string;
private data:Array<any> = [[]];
private data:Array<any> = Array(8).fill(0).map(() => { return { data: [], label: [] } });
private labels:Array<any> = [];
private colors:Array<any> = this.chartService.getColors();
private channels:Array<string> = this.chartService.getChannels();
private options:any = this.chartService.getChartJSDefaults();
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;
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);
this.socket.removeListener(
this.constants.socket.events.fft
);
}
}
Ver Arquivo
+3 -1
Ver Arquivo
@@ -17,7 +17,9 @@ export class MotionComponent implements OnInit {
socket: any;
viewer: any;
model: any;
constructor(private view: ElementRef, private constants: Constants) {
constructor(private view: ElementRef,
private constants: Constants) {
this.socket = io(constants.socket.url);
}
+89 -23
Ver Arquivo
@@ -44,26 +44,92 @@ export class ChartService {
}, overrides);
}
getChartJSDefaults (overrides: any = {}): any {
getChartJSGlobalDefaults (overrides: any = {}): any {
return Object.assign({
responsive: true,
animation: false,
animationSteps: 15,
datasetStrokeWidth: 1,
pointDot: false,
pointDotRadius: 1,
pointDotStrokeWidth: 0,
datasetFill: false,
scaleOverride: true,
scaleStartValue: -2,
scaleStepWidth: 1,
scaleSteps: 6,
barShowStroke: false,
barValueSpacing: 1,
barStrokeWidth: 1
legend: {
display: true,
position: 'bottom'
},
}, overrides);
}
getChartJSLineDefaults (overrides: any = {}): any {
return Object.assign({
scales: {
xAxes: [{
gridLines: {
display: true
},
ticks: {
max: 4,
min: -2,
stepSize: 0.5
}
}],
yAxes: [{
gridLines: {
display: true
},
ticks: {
max: 4,
min: -2,
stepSize: 0.5
}
}]
}
}, this.getChartJSGlobalDefaults());
}
getChartJSBarDefaults (overrides: any = {}): any {
return Object.assign({
scales: {
xAxes: [{
gridLines: {
display: false
},
ticks: {
max: 4,
min: 0,
stepSize: 0.5
}
}],
yAxes: [{
gridLines: {
display: false
},
ticks: {
max: 4,
min: 0,
stepSize: 0.5
}
}]
}
}, this.getChartJSGlobalDefaults({
legend: {
display: false
}
}));
}
getChartJSRadarDefaults (overrides: any = {}): any {
return Object.assign({
scale: {
lineArc: false,
angleLines: {
display: true
},
ticks: {
max: 4,
min: -2,
stepSize: 0.5,
showLabelBackdrop: false
}
}
}, this.getChartJSGlobalDefaults());
}
getChartSmoothieDefaults (overrides: any = {}): any {
return Object.assign({
millisPerLine: 3000,
@@ -88,18 +154,18 @@ export class ChartService {
getColors (): Array<any> {
return [
{ strokeColor: 'rgba(112,185,252,1)', fillColor: 'rgba(112,185,252,1)' },
{ strokeColor: 'rgba(116,150,161,1)', fillColor: 'rgba(116,150,161,1)' },
{ strokeColor: 'rgba(162,86,178,1)', fillColor: 'rgba(162,86,178,1)' },
{ strokeColor: 'rgba(144,132,246,1)', fillColor: 'rgba(144,132,246,1)' },
{ strokeColor: 'rgba(138,219,229,1)', fillColor: 'rgba(138,219,229,1)' },
{ strokeColor: 'rgba(232,223,133,1)', fillColor: 'rgba(232,223,133,1)' },
{ strokeColor: 'rgba(148,159,177,1)', fillColor: 'rgba(148,159,177,1)' },
{ strokeColor: 'rgba(77,83,96,1)', fillColor: 'rgba(77,83,96,1)' }
{ 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> {
getColorByIndex (index: number): Array<any> {
return this.getColors().filter((c, i) => index === i);
}
+1
Ver Arquivo
@@ -1,2 +1,3 @@
export * from './chart.service';
export * from './ng2-charts';
export * from './constants';
+229 -208
Ver Arquivo
@@ -1,119 +1,51 @@
import {
Component, OnDestroy, OnInit, OnChanges,
EventEmitter, ElementRef, Input
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: 'chart',
template: `<canvas></canvas>`,
directives: [CORE_DIRECTIVES, NgClass]
})
export class ChartsComponent {}
@Component({
selector: 'base-chart',
properties: [
'data',
'labels',
'series',
'colours',
'chartType',
'legend',
'options'
],
events: ['chartClick', 'chartHover'],
template: `
<canvas style="width: 100%; height: 100%;" (click)="click($event)" (mousemove)="hover($event)"></canvas>
`,
template: `<canvas style="width: 100%; height: 100%;"></canvas>`,
directives: [CORE_DIRECTIVES, FORM_DIRECTIVES, NgClass]
})
export class BaseChartComponent implements OnInit, OnDestroy, OnChanges {
@Input() public data:Array<any> = [];
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 series:Array<any> = [];
@Input() public colours:Array<any> = [];
@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 legendTemplate:any;
private initFlag:boolean = false;
private chartClick:EventEmitter<any> = new EventEmitter();
private chartHover:EventEmitter<any> = new EventEmitter();
private defaultsColours:Array<any> = [
{
fillColor: 'rgba(151,187,205,0.2)',
strokeColor: 'rgba(151,187,205,1)',
pointColor: 'rgba(151,187,205,1)',
pointStrokeColor: '#fff',
pointHighlightFill: '#fff',
pointHighlightStroke: 'rgba(151,187,205,0.8)',
color: 'rgba(151,187,205,1)',
highlight: 'rgba(151,187,205,0.8)'
}, {
fillColor: 'rgba(220,220,220,0.2)',
strokeColor: 'rgba(220,220,220,1)',
pointColor: 'rgba(220,220,220,1)',
pointStrokeColor: '#fff',
pointHighlightFill: '#fff',
pointHighlightStroke: 'rgba(220,220,220,0.8)',
color: 'rgba(220,220,220,1)',
highlight: 'rgba(220,220,220,0.8)'
}, {
fillColor: 'rgba(247,70,74,0.2)',
strokeColor: 'rgba(247,70,74,1)',
pointColor: 'rgba(247,70,74,1)',
pointStrokeColor: '#fff',
pointHighlightFill: '#fff',
pointHighlightStroke: 'rgba(247,70,74,0.8)',
color: 'rgba(247,70,74,1)',
highlight: 'rgba(247,70,74,0.8)'
}, {
fillColor: 'rgba(70,191,189,0.2)',
strokeColor: 'rgba(70,191,189,1)',
pointColor: 'rgba(70,191,189,1)',
pointStrokeColor: '#fff',
pointHighlightFill: '#fff',
pointHighlightStroke: 'rgba(70,191,189,0.8)',
color: 'rgba(70,191,189,1)',
highlight: 'rgba(70,191,189,0.8)'
}, {
fillColor: 'rgba(253,180,92,0.2)',
strokeColor: 'rgba(253,180,92,1)',
pointColor: 'rgba(253,180,92,1)',
pointStrokeColor: '#fff',
pointHighlightFill: '#fff',
pointHighlightStroke: 'rgba(253,180,92,0.8)',
color: 'rgba(253,180,92,1)',
highlight: 'rgba(253,180,92,0.8)'
}, {
fillColor: 'rgba(148,159,177,0.2)',
strokeColor: 'rgba(148,159,177,1)',
pointColor: 'rgba(148,159,177,1)',
pointStrokeColor: '#fff',
pointHighlightFill: '#fff',
pointHighlightStroke: 'rgba(148,159,177,0.8)',
color: 'rgba(148,159,177,1)',
highlight: 'rgba(148,159,177,0.8)'
}, {
fillColor: 'rgba(77,83,96,0.2)',
strokeColor: 'rgba(77,83,96,1)',
pointColor: 'rgba(77,83,96,1)',
pointStrokeColor: '#fff',
pointHighlightFill: '#fff',
pointHighlightStroke: 'rgba(77,83,96,0.8)',
color: 'rgba(77,83,96,1)',
highlight: 'rgba(77,83,96,0.8)'
}];
private element:ElementRef;
public constructor(element:ElementRef) {
this.element = element;
}
@@ -122,8 +54,10 @@ export class BaseChartComponent implements OnInit, OnDestroy, OnChanges {
this.ctx = this.element.nativeElement.children[0].getContext('2d');
this.cvs = this.element.nativeElement.children[0];
this.parent = this.element.nativeElement;
this.refresh();
this.initFlag = true;
if (this.data || this.datasets) {
this.create(this.ctx);
}
}
public ngOnChanges():any {
@@ -137,129 +71,216 @@ export class BaseChartComponent implements OnInit, OnDestroy, OnChanges {
this.chart.destroy();
this.chart = void 0;
}
if (this.legendTemplate) {
this.legendTemplate.destroy();
this.legendTemplate = void 0;
}
}
public setLegend():void {
let list = this.parent.getElementsByTagName('ul');
if (list.length) {
list[0].remove();
this.parent.insertAdjacentHTML('beforeend', this.chart.generateLegend());
} else {
this.parent.insertAdjacentHTML('beforeend', this.chart.generateLegend());
}
}
public create(ctx:any):any {
let datasets:any = void 0;
public getColour(colour:Array<number>):any {
return {
fillColor: this.rgba(colour, 0.2),
strokeColor: this.rgba(colour, 1),
pointColor: this.rgba(colour, 1),
pointStrokeColor: '#fff',
pointHighlightFill: '#fff',
pointHighlightStroke: this.rgba(colour, 0.8),
color: this.rgba(colour, 1),
highlight: this.rgba(colour, 0.8)
// 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}`);
}
const 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});
};
}
const opts = {
type: this.chartType,
data: {
labels: this.labels,
datasets: datasets
},
options: options
};
}
public getRandomInt(min:number, max:number):number {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
public rgba(colour:Array<number>, alpha:number):string {
return 'rgba(' + colour.concat(alpha).join(',') + ')';
}
public click(evt:any):void {
let atEvent = this.chart.getPointsAtEvent || this.chart.getBarsAtEvent || this.chart.getSegmentsAtEvent;
let activePoints = atEvent.call(this.chart, evt);
if (activePoints.length > 0) {
let activeLabel = activePoints[0].label;
this.chartClick.emit({activePoints: activePoints, activeLabel: activeLabel});
}
}
public hover(evt:any):void {
let atEvent = this.chart.getPointsAtEvent || this.chart.getBarsAtEvent || this.chart.getSegmentsAtEvent;
let activePoints = atEvent.call(this.chart, evt);
if (activePoints.length > 0) {
let activeLabel = activePoints[0].label;
let activePoint = activePoints[0].value;
this.chartHover.emit({activePoints: activePoints, activePoint: activePoint, activeLabel: activeLabel});
}
}
public getChartBuilder(ctx:any, data:Array<any>, options:any):any {
return new Chart(ctx)[this.chartType](data, options);
}
public getDataObject(label:string, value:any):any {
if (this.chartType === 'Line'
|| this.chartType === 'Bar'
|| this.chartType === 'Radar') {
return {
label: label,
data: value
};
if (typeof Chart === 'undefined') {
throw new Error('ng2-charts configuration issue: Embedding Chart.js lib is mandatory');
}
if (this.chartType === 'Pie'
|| this.chartType === 'Doughnut'
|| this.chartType === 'PolarArea') {
return {
label: label,
value: value
};
}
return void 0;
}
public getChartData(labels:any, dataObject:any):any {
if (this.chartType === 'Line'
|| this.chartType === 'Bar'
|| this.chartType === 'Radar') {
return {
labels: labels,
datasets: dataObject
};
}
if (this.chartType === 'Pie'
|| this.chartType === 'Doughnut'
|| this.chartType === 'PolarArea') {
return dataObject;
}
this.chart = new Chart(ctx, opts);
}
private refresh():any {
if (this.options.responsive && this.parent.clientHeight === 0) {
return setTimeout(() => this.refresh(), 50);
}
this.ngOnDestroy();
let dataset:Array<any> = [];
for (let i = 0; i < this.data.length; i++) {
let colourDesc:Array<number> = [this.getRandomInt(0, 255), this.getRandomInt(0, 255), this.getRandomInt(0, 255)];
let colour = i < this.colours.length ? this.colours[i] : this.defaultsColours[i] || this.getColour(colourDesc);
let data:any = Object.assign(colour,
this.getDataObject(this.series[i] || this.labels[i], this.data[i]));
dataset.push(data);
}
let data:any = this.getChartData(this.labels, dataset);
this.chart = this.getChartBuilder(this.ctx, data, this.options);
if (this.legend) {
this.setLegend();
}
this.chart.data.labels = [...this.labels];
this.chart.data.datasets = [...this.chart.data.datasets.map((set, index) => {
return Object.assign(set, this.datasets[index]);
})];
this.chart.update();
}
}
export const CHART_DIRECTIVES:Array<any> = [ChartsComponent, BaseChartComponent];
// 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];
@@ -6,7 +6,6 @@
box-shadow: 0 0 5px rgba(0,0,0,0.3);
background-color: #333333;
position: relative;
height: 100%;
width: 1060px;
overflow: hidden;
}
+2 -2
Ver Arquivo
@@ -3,7 +3,7 @@
<aside class="time-series-channels">
<ul>
<li *ngFor="let channel of channels, let i = index"
[ngStyle]="{ 'color': colors[i].strokeColor }">
[ngStyle]="{ 'color': colors[i].borderColor }">
{{ channel }}
</li>
</ul>
@@ -12,7 +12,7 @@
<aside class="time-series-amplitudes">
<ul>
<li *ngFor="let amplitude of amplitudes, let i = index"
[ngStyle]="{ 'color': colors[i].strokeColor }">
[ngStyle]="{ 'color': colors[i].borderColor }">
{{ amplitude }}
</li>
</ul>
+13 -10
Ver Arquivo
@@ -1,8 +1,8 @@
import { Component, ElementRef, OnInit, OnDestroy } from '@angular/core';
import { Component, ElementRef } from '@angular/core';
import { OnInit, OnDestroy } from '@angular/core';
import { SmoothieChart, TimeSeries } from 'smoothie';
import { ChartService } from '../shared';
import { ChartService, Constants } from '../shared';
import * as io from 'socket.io-client';
import { Constants } from '../shared/constants';
@Component({
moduleId: module.id,
@@ -12,7 +12,7 @@ import { Constants } from '../shared/constants';
providers: [ChartService, Constants]
})
export class TimeSeriesComponent implements OnInit {
export class TimeSeriesComponent implements OnInit, OnDestroy {
socket: any;
constructor(private view: ElementRef,
@@ -23,13 +23,12 @@ export class TimeSeriesComponent implements OnInit {
}
private options = this.chartService.getChartSmoothieDefaults();
private channels = this.chartService.getChannels();
private colors = this.chartService.getColors();
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();
@@ -42,17 +41,21 @@ export class TimeSeriesComponent implements OnInit {
}
ngOnDestroy () {
this.socket.removeListener(this.constants.socket.events.time);
this.socket.removeListener(
this.constants.socket.events.time
);
}
ngAfterViewInit () {
this.timeSeries.streamTo(this.view.nativeElement.querySelector('canvas'), 40);
this.timeSeries.streamTo(
this.view.nativeElement.querySelector('#timeSeries')
);
}
addTimeSeriesLines () {
this.lines.forEach((line, index) => {
this.timeSeries.addTimeSeries(line, {
strokeStyle: this.colors[index].strokeColor
strokeStyle: this.colors[index].borderColor
});
});
}
+7 -4
Ver Arquivo
@@ -1,7 +1,6 @@
import { Component, OnInit, OnDestroy, ElementRef } from '@angular/core';
import * as io from 'socket.io-client';
import { Constants } from '../shared/constants';
import { ChartService } from '../shared';
import { ChartService, Constants } from '../shared';
declare var chroma: any;
declare var Plotly: any;
@@ -19,7 +18,9 @@ export class TopoComponent implements OnInit {
socket: any;
plotElement: any;
constructor(private view: ElementRef, private chartService: ChartService, private constants: Constants) {
constructor(private view: ElementRef,
private chartService: ChartService,
private constants: Constants) {
this.socket = io(constants.socket.url);
}
@@ -46,7 +47,9 @@ export class TopoComponent implements OnInit {
}
ngOnDestroy (): void {
this.socket.removeListener(this.constants.socket.events.topo);
this.socket.removeListener(
this.constants.socket.events.topo
);
}
getGrid (n) {
+2 -2
Ver Arquivo
@@ -13,7 +13,7 @@
font-family: 'Roboto', sans-serif;
font-weight: 300;
color: #ffffff;
background-color: #222222;
background-color: rgba(30, 30, 30, 1);
margin: 0;
}
</style>
@@ -33,7 +33,7 @@
<body>
<bci-dashboard>Loading...</bci-dashboard>
<script src="vendor/chart.js/Chart.js"></script>
<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>
+7 -5
Ver Arquivo
@@ -8,15 +8,17 @@ module.exports = {
sampleEvent: 'sample'
},
signal: {
bins: 128,
bufferSize: 128,
bufferSize: 256,
sampleRate: 250,
windowRefreshRate: 8 // samples
windowSize: 32 // data has a moving window of 32 samples = 128 milliseconds (250Hz)
},
fft: {
bins: 256
},
scale: {
global: 1.5,
global: 1,
simulated: 4,
skipLabels: 8
skipLabels: 4
},
units: {
hertz: 'Hz',
+1 -1
Ver Arquivo
@@ -8,7 +8,7 @@ module.exports = class FFT {
constructor ({ Signal }) {
this.signal = Signal;
this.bins = constants.signal.bins;
this.bins = constants.fft.bins;
this.bufferSize = constants.signal.bufferSize;
this.sampleRate = constants.signal.sampleRate;
this.bands = constants.bands;
+19 -21
Ver Arquivo
@@ -8,44 +8,42 @@ module.exports = class TimeSeries {
constructor ({ Signal }) {
this.signal = Signal;
this.sampleRate = constants.signal.sampleRate;
this.windowSize = constants.time.windowSize;
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.create(constants.connector.channels);
this.timeSeries = [];
this.amplitudes = [];
this.subscribe();
}
subscribe () {
this.signal.emitter.on(constants.events.signal, (signal) => {
this.offsetForGrid(signal);
this.signalToAmplitudes(signal);
this.timeSeries = signal;
this.filter();
this.offset();
this.trim();
this.signalToAmplitudes(signal);
this.emit();
});
}
create (channelAmount) {
let timeSeries = new Array(channelAmount).fill([]);
return timeSeries.map((channel, channelNumber) => {
return new Array((this.sampleRate * this.windowSize))
.fill(0)
.map((amplitude) => {
return Utils.signal.offsetForGrid(amplitude, channelNumber, constants.connector.channels, this.signal.scale);
});
});
}
offsetForGrid (signal) {
this.timeSeries.forEach((channel, channelIndex) => {
let offset = signal[channelIndex][signal[channelIndex].length - 1];
channel.push(Utils.signal.offsetForGrid(offset, channelIndex, constants.connector.channels, this.signal.scale));
channel.shift();
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.process(signal);
signal = Utils.filter.highpass(signal);
});
}
trim () {
this.timeSeries.forEach((channel) => {
channel = channel.splice(0, this.bufferSize - this.windowSize);
});
}
+3 -5
Ver Arquivo
@@ -11,10 +11,8 @@ module.exports = class Signal {
constructor ({ io }) {
this.io = io;
this.emitter = new SignalEmitter();
this.bins = constants.signal.bins;
this.bufferSize = constants.signal.bufferSize;
this.windowRefreshRate = constants.signal.windowRefreshRate;
this.windowSize = this.bins / this.windowRefreshRate;
this.windowSize = constants.signal.windowSize;
this.sampleRate = constants.signal.sampleRate;
this.signals = [[],[],[],[],[],[],[],[]];
this.sampleNumber = 0;
@@ -35,7 +33,7 @@ module.exports = class Signal {
this.sampleNumber++;
this.add(sample);
if (this.sampleNumber === this.bins) {
if (this.sampleNumber === this.bufferSize) {
this.emitter.emit(constants.events.signal, [...this.signals]);
this.window();
}
@@ -54,7 +52,7 @@ module.exports = class Signal {
return index > (this.windowSize - 1);
});
});
this.sampleNumber = this.bins - this.windowSize;
this.sampleNumber = this.bufferSize - this.windowSize;
}
setScale () {
+21 -1
Ver Arquivo
@@ -25,13 +25,33 @@ module.exports = {
let filter = key.toLowerCase();
if (filter in this && typeof this[filter] === 'function') {
// @TODO: apply all filters dynamically
//signal = this[signal](signal);
//signal = this[filter](signal);
}
});
signal = this.notch(signal);
return signal;
},
highpass (signal) {
var iirCalculator = new Fili.CalcCascades();
var hpFilterCoeffs = iirCalculator.highpass({
order: 4, // cascade 4 biquad filters (max: 12)
characteristic: 'butterworth',
Fs: 250, // sampling frequency
Fc: 1,
gain: 0, // gain for peak, lowshelf and highshelf
preGain: false // adds one constant multiplication for highpass and lowpass
// k = (1 + cos(omega)) * 0.5 / k = 1 with preGain == false
});
var hpFilter = new Fili.IirFilter(hpFilterCoeffs);
return hpFilter.multiStep(signal);
},
notch (signal) {
if (this.state.NOTCH === 'NONE') return signal;
-1
Ver Arquivo
@@ -15,7 +15,6 @@ module.exports = {
},
offsetForGrid (amplitude, channelNumber, channelAmount = 8, scale = 1.5) {
// @TODO: initial dataset is returning messed up values
let scaledAmplitude = amplitude * Math.pow(10, scale);
let offset = 2 * (channelAmount - channelNumber) - 1;
return parseFloat(scaledAmplitude + offset);
+1
Ver Arquivo
@@ -4,6 +4,7 @@
/** Map relative paths to URLs. */
const map: any = {
'smoothie': 'vendor/smoothie/smoothie.js',
'chartjs': 'vendor/chart.js/dist/Chart.bundle.js',
'ng2-charts': 'vendor/ng2-charts/bundles/ng2-charts.js',
'socket.io-client': 'vendor/socket.io-client/socket.io.js',
'chroma-js': 'vendor/chroma-js/chroma.js',