mirror of
https://gitlab.silvrtree.co.uk/martind2000/censis-archive.git
synced 2025-02-04 09:10:15 +00:00
”2016-06-21”
This commit is contained in:
parent
b5070a8b71
commit
a6ca6b48ec
11
sensortoy/sensortoy/.gitignore
vendored
11
sensortoy/sensortoy/.gitignore
vendored
@ -178,9 +178,9 @@ xcuserdata
|
||||
*.xcuserstate
|
||||
|
||||
dist
|
||||
www/libs
|
||||
/www/
|
||||
/app/libs/
|
||||
www
|
||||
# app/libs
|
||||
|
||||
### Android template
|
||||
# Built application files
|
||||
*.apk
|
||||
@ -228,3 +228,8 @@ captures/
|
||||
.dropbox.attr
|
||||
.dropbox.cache
|
||||
|
||||
/app/libs/mui/
|
||||
/app/libs/bluebird/
|
||||
/app/libs/ejs/
|
||||
/app/libs/jquery/
|
||||
/platforms/android/assets
|
||||
|
@ -95,6 +95,10 @@
|
||||
<script type="text/javascript" src="libs/ejs/ejs.js"></script>
|
||||
<script type="text/javascript"
|
||||
src="libs/bluebird/js/browser/bluebird.js"></script>
|
||||
|
||||
<script type="text/javascript" src="libs/jsfft/complex_array.js"></script>
|
||||
<script type="text/javascript" src="libs/jsfft/fft.js"></script>
|
||||
|
||||
<!-- endbuild -->
|
||||
|
||||
<!-- build:standards -->
|
||||
|
@ -109,12 +109,13 @@ CC2650_ACCEL = function(p) {
|
||||
this.data.mag.y = this.storeData(calcData.mag.y, this.data.mag.y);
|
||||
this.data.mag.z = this.storeData(calcData.mag.z, this.data.mag.z);
|
||||
|
||||
// console.log(JSON.stringify(this.data));
|
||||
// Console.log(JSON.stringify(this.data));
|
||||
// Console.log(this.state);
|
||||
};
|
||||
|
||||
this.startService = function() {
|
||||
|
||||
this.maxLength = 255;
|
||||
if (this.deviceID !== null) {
|
||||
|
||||
console.log('Starting CC2650 Accelerometer Service on ', this.deviceID);
|
||||
@ -154,8 +155,9 @@ CC2650_ACCEL = function(p) {
|
||||
|
||||
};
|
||||
|
||||
this.advancedGraph = function(mode, data, subID) {
|
||||
|
||||
this.advancedGraphFFT = function(mode, data, subID) {
|
||||
var xstep;
|
||||
var scalePos;
|
||||
var lm;
|
||||
var ceiling;
|
||||
var elm;
|
||||
@ -169,6 +171,119 @@ CC2650_ACCEL = function(p) {
|
||||
var lineID;
|
||||
var max;
|
||||
|
||||
var reducer = function(p, v) {
|
||||
return (Math.abs(p) > Math.abs(v) ? Math.abs(p) : Math.abs(v));
|
||||
};
|
||||
|
||||
|
||||
var parts = ['x', 'y', 'z'];
|
||||
var _newData = {x: [],y: [],z: []};
|
||||
|
||||
_data = data || this.data;
|
||||
|
||||
_subID = subID || '';
|
||||
|
||||
// LineID = [this.frameID , _subID , '-line'].join('');
|
||||
text1ID = [this.frameID, _subID, '-txt1'].join('');
|
||||
text2ID = [this.frameID, _subID, '-txt2'].join('');
|
||||
ceiling = 0;
|
||||
if (_data.x.length > 0) {
|
||||
|
||||
max = 2;
|
||||
|
||||
for (var lineMode = 0; lineMode < parts.length; lineMode++) {
|
||||
|
||||
|
||||
lm = parts[lineMode];
|
||||
|
||||
var data = new complex_array.ComplexArray(_data[lm]);
|
||||
|
||||
|
||||
data.FFT();
|
||||
|
||||
data.forEach(function(c_value, i) {
|
||||
ceiling = reducer(c_value.real ,ceiling);
|
||||
_newData[lm].push(c_value.real);
|
||||
});
|
||||
|
||||
//Ceiling = _data[lm].reduce(reducer);
|
||||
|
||||
console.log('ceiling:', ceiling);
|
||||
|
||||
|
||||
}
|
||||
|
||||
if (ceiling > 500) {
|
||||
max = (ceiling > max) ? (Math.ceil((Math.round(ceiling) + 1) / 50) * 50) : max;
|
||||
} else {
|
||||
max = (ceiling > max) ? (Math.ceil((Math.round(ceiling) + 1) / 10) * 10) : max;
|
||||
}
|
||||
ceiling = max;
|
||||
|
||||
floor = ceiling * -1;
|
||||
ceilingLimit = ceiling;
|
||||
console.log('ceiling:',ceiling);
|
||||
|
||||
scalePos = (124 / 2) / ceiling;
|
||||
|
||||
xstep = (680 - 46) / this.maxLength;
|
||||
|
||||
//Var xstep = 2.34;
|
||||
|
||||
for (lineMode = 0; lineMode < parts.length; lineMode++) {
|
||||
|
||||
lm = parts[lineMode];
|
||||
var startX = 46 + (this.maxLength - _newData[lm].length) * xstep;
|
||||
|
||||
calcArray = [];
|
||||
|
||||
lineID = this.frameID + _subID + '-' + lm + '-line';
|
||||
for (var x = 0; x < _newData[lm].length; x++) {
|
||||
|
||||
calcArray.push((startX + (x * xstep)).toFixed(2) + ',' + (71 - ((_newData[lm][x]) * scalePos)).toFixed(
|
||||
2));
|
||||
|
||||
}
|
||||
|
||||
elm = document.getElementById(lineID);
|
||||
|
||||
elm.setAttribute('points', calcArray.join(' '));
|
||||
|
||||
}
|
||||
|
||||
elm = document.getElementById(text1ID);
|
||||
elm.textContent = ceilingLimit;
|
||||
|
||||
elm = document.getElementById(text2ID);
|
||||
elm.textContent = floor;
|
||||
|
||||
}
|
||||
|
||||
this.previousCeil = ceiling;
|
||||
};
|
||||
|
||||
|
||||
this.advancedGraph = function(mode, data, subID) {
|
||||
|
||||
var xstep;
|
||||
var scalePos;
|
||||
var lm;
|
||||
var ceiling;
|
||||
var elm;
|
||||
var text2ID;
|
||||
var ceilingLimit;
|
||||
var calcArray;
|
||||
var floor;
|
||||
var _subID;
|
||||
var _data;
|
||||
var text1ID;
|
||||
var lineID;
|
||||
var max;
|
||||
|
||||
var reducer = function(p, v) {
|
||||
return (Math.abs(p) > Math.abs(v) ? Math.abs(p) : Math.abs(v));
|
||||
};
|
||||
|
||||
|
||||
var parts = ['x', 'y', 'z'];
|
||||
|
||||
@ -186,10 +301,11 @@ CC2650_ACCEL = function(p) {
|
||||
|
||||
for (var lineMode = 0; lineMode < parts.length; lineMode++) {
|
||||
lm = parts[lineMode];
|
||||
ceiling = _data[lm].reduce(function(p, v) {
|
||||
/*Ceiling = _data[lm].reduce(function(p, v) {
|
||||
return (Math.abs(p) > Math.abs(v) ? Math.abs(p) : Math.abs(v));
|
||||
});
|
||||
});*/
|
||||
|
||||
ceiling = _data[lm].reduce(reducer);
|
||||
|
||||
if (ceiling > 500) {
|
||||
max = (ceiling > max) ? (Math.ceil((Math.round(ceiling) + 1) / 50) * 50) : max;
|
||||
@ -203,16 +319,16 @@ CC2650_ACCEL = function(p) {
|
||||
floor = ceiling * -1;
|
||||
ceilingLimit = ceiling;
|
||||
|
||||
var scalePos = (124 / 2) / ceiling;
|
||||
var scaleNeg = (124 / 2) / floor;
|
||||
var xstep = (680 - 46) / 100;
|
||||
scalePos = (124 / 2) / ceiling;
|
||||
|
||||
xstep = (680 - 46) / this.maxLength;
|
||||
|
||||
//Var xstep = 2.34;
|
||||
|
||||
for (lineMode = 0; lineMode < parts.length; lineMode++) {
|
||||
|
||||
lm = parts[lineMode];
|
||||
var startX = 46 + (100 - _data[lm].length) * xstep;
|
||||
var startX = 46 + (this.maxLength - _data[lm].length) * xstep;
|
||||
|
||||
calcArray = [];
|
||||
|
||||
@ -245,7 +361,7 @@ CC2650_ACCEL = function(p) {
|
||||
// Nothing to animate yet
|
||||
|
||||
// return -1;
|
||||
this.advancedGraph(0, this.data.gyro, 'gyro');
|
||||
this.advancedGraphFFT(0, this.data.gyro, 'gyro');
|
||||
this.advancedGraph(0, this.data.accel, 'accel');
|
||||
this.advancedGraph(0, this.data.mag, 'mag');
|
||||
// This.simpleGraph(this.data.temp, 'temp');
|
||||
@ -314,6 +430,10 @@ CC2650_ACCEL = function(p) {
|
||||
|
||||
this.insertFrame = function(mode) {
|
||||
|
||||
var _graph;
|
||||
var row;
|
||||
var button;
|
||||
var elm;
|
||||
var frame;
|
||||
var title;
|
||||
var modeID = mode + 'ID';
|
||||
@ -322,7 +442,6 @@ CC2650_ACCEL = function(p) {
|
||||
var titles = {
|
||||
gyro: 'Gyroscope', accel: 'Accelerometer', mag: 'Magnetometer'
|
||||
};
|
||||
var modes = ['gyro', 'accel', 'mag'];
|
||||
|
||||
console.log('FrameID: ', this.frames[modeID]);
|
||||
|
||||
@ -333,12 +452,17 @@ CC2650_ACCEL = function(p) {
|
||||
});
|
||||
|
||||
if (mode === 'mag') {
|
||||
var elm = $('<div />', {class: 'mui-row'});
|
||||
var button = $('<button />',{class: 'mui-btn mui-btn--raised', id: modeID + '-cal', text: 'Calibrate', click: this.startCalibrate.bind(this)});
|
||||
elm = $('<div />', {class: 'mui-row'});
|
||||
button = $('<button />', {
|
||||
class: 'mui-btn mui-btn--raised',
|
||||
id: modeID + '-cal',
|
||||
text: 'Calibrate',
|
||||
click: this.startCalibrate.bind(this)
|
||||
});
|
||||
|
||||
elm.append($('<div />', { class: 'mui-col-xs-8 mui--text-title mui-ellipsis-2', text: title}));
|
||||
|
||||
// elm.append($('<div />', {class: 'mui-col-xs-4 mui--text-right'}).append(button));
|
||||
// Elm.append($('<div />', {class: 'mui-col-xs-4 mui--text-right'}).append(button));
|
||||
|
||||
frame.append(elm);
|
||||
|
||||
@ -353,7 +477,7 @@ CC2650_ACCEL = function(p) {
|
||||
|
||||
// Call the parent displayForm first...
|
||||
|
||||
var row = $('<div />', {class: 'mui-row'});
|
||||
row = $('<div />', {class: 'mui-row'});
|
||||
|
||||
$('<div />', {
|
||||
class: 'mui-col-xs-4 mui--text-accent mui--text-center', text: 'X'
|
||||
@ -393,7 +517,7 @@ CC2650_ACCEL = function(p) {
|
||||
|
||||
row = $('<div />', {class: 'mui-row'});
|
||||
|
||||
var _graph = this.generateBlankGraph(mode);
|
||||
_graph = this.generateBlankGraph(mode);
|
||||
row.append($('<div>', {class: 'mui-col-sm-12'}).append(_graph));
|
||||
|
||||
this.$id[modeID].append(row);
|
||||
|
@ -87,15 +87,20 @@ var CC2650_HUM = function(p) {
|
||||
};
|
||||
|
||||
this.animateGraph = function() {
|
||||
this.simpleGraph(this.data.temp, 'temp');
|
||||
/*
|
||||
This.simpleGraph(this.data.temp, 'temp');
|
||||
this.simpleGraph(this.data.humidity, 'humidity');
|
||||
};
|
||||
*/
|
||||
var arcTemp = this.frameID + 'temp-arc';
|
||||
var arcHumidity = this.frameID + 'humidity-arc';
|
||||
this.updateArc(this.data.temp, 'temp' , arcTemp, '°C', 50);
|
||||
this.updateArc(this.data.humidity, 'humidity' , arcHumidity, '%RH', 100);
|
||||
|
||||
};
|
||||
|
||||
this.insertFrame = function() {
|
||||
|
||||
var self = this;
|
||||
var blankChart;
|
||||
|
||||
// Call the parent displayForm first...
|
||||
this.superClass_.insertFrame.call(self);
|
||||
@ -103,13 +108,28 @@ var CC2650_HUM = function(p) {
|
||||
var temp = this.frameID + '-t';
|
||||
var humidity = this.frameID + '-h';
|
||||
|
||||
var arcB = this.frameID + 'temp-arcB';
|
||||
var arcTemp = this.frameID + 'temp-arc';
|
||||
|
||||
|
||||
var humidityArcB = this.frameID + 'humidity-arcB';
|
||||
var arcHumidity = this.frameID + 'humidity-arc';
|
||||
|
||||
|
||||
var settings = {data: {baseID: this.frameID}};
|
||||
var html = new EJS({url: './partials/cc2650_humidity.ejs'}).render(settings);
|
||||
var html = new EJS({url: './partials/cc2650_humidity_dial.ejs'}).render(settings);
|
||||
|
||||
this.$id.append(html);
|
||||
|
||||
document.getElementById(arcB).setAttribute('d', this.describeArc(150, 150, 100, 0, 240));
|
||||
document.getElementById(arcTemp).setAttribute('d', this.describeArc(150, 150, 100, 0, this.setArc(0)));
|
||||
|
||||
document.getElementById(humidityArcB).setAttribute('d', this.describeArc(150, 150, 100, 0, 240));
|
||||
document.getElementById(arcHumidity).setAttribute('d', this.describeArc(150, 150, 100, 0, this.setArc(0)));
|
||||
|
||||
|
||||
/*
|
||||
var row = $('<div />', {class: 'mui-row'});
|
||||
Var row = $('<div />', {class: 'mui-row'});
|
||||
|
||||
$('<div />',
|
||||
{class: 'mui-col-xs-3 mui--text-accent mui--text-right', text: 'Temp:'}).appendTo(row);
|
||||
|
@ -6,7 +6,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
/* global MANUFACTUREDECODER */
|
||||
/* global */
|
||||
/* jshint browser: true , devel: true*/
|
||||
|
||||
/**
|
||||
|
@ -45,6 +45,7 @@ var CAPABILITY = function(p) {
|
||||
|
||||
this.target = null;
|
||||
this.$frame = null;
|
||||
this.maxLength = 99;
|
||||
|
||||
this.previousCeil = 0;
|
||||
|
||||
@ -114,7 +115,7 @@ CAPABILITY.prototype.storeData = function(data, alt) {
|
||||
}
|
||||
var target = alt || this.data;
|
||||
|
||||
if (target.length === 99) {
|
||||
if (target.length === this.maxLength) {
|
||||
target = target.slice(1);
|
||||
}
|
||||
|
||||
|
116
sensortoy/sensortoy/app/libs/jsfft/complex_array.js
Normal file
116
sensortoy/sensortoy/app/libs/jsfft/complex_array.js
Normal file
@ -0,0 +1,116 @@
|
||||
'use strict';
|
||||
|
||||
!function(exports, undefined) {
|
||||
|
||||
var
|
||||
// If the typed array is unspecified, use this.
|
||||
DefaultArrayType = Float32Array,
|
||||
// Simple math functions we need.
|
||||
sqrt = Math.sqrt,
|
||||
sqr = function(number) {return Math.pow(number, 2)},
|
||||
// Internal convenience copies of the exported functions
|
||||
isComplexArray,
|
||||
ComplexArray
|
||||
|
||||
exports.isComplexArray = isComplexArray = function(obj) {
|
||||
return obj !== undefined &&
|
||||
obj.hasOwnProperty !== undefined &&
|
||||
obj.hasOwnProperty('real') &&
|
||||
obj.hasOwnProperty('imag')
|
||||
}
|
||||
|
||||
exports.ComplexArray = ComplexArray = function(other, opt_array_type){
|
||||
if (isComplexArray(other)) {
|
||||
// Copy constuctor.
|
||||
this.ArrayType = other.ArrayType
|
||||
this.real = new this.ArrayType(other.real)
|
||||
this.imag = new this.ArrayType(other.imag)
|
||||
} else {
|
||||
this.ArrayType = opt_array_type || DefaultArrayType
|
||||
// other can be either an array or a number.
|
||||
this.real = new this.ArrayType(other)
|
||||
this.imag = new this.ArrayType(this.real.length)
|
||||
}
|
||||
|
||||
this.length = this.real.length
|
||||
}
|
||||
|
||||
ComplexArray.prototype.toString = function() {
|
||||
var components = []
|
||||
|
||||
this.forEach(function(c_value, i) {
|
||||
components.push(
|
||||
'(' +
|
||||
c_value.real.toFixed(2) + ',' +
|
||||
c_value.imag.toFixed(2) +
|
||||
')'
|
||||
)
|
||||
})
|
||||
|
||||
return '[' + components.join(',') + ']'
|
||||
}
|
||||
|
||||
// In-place mapper.
|
||||
ComplexArray.prototype.map = function(mapper) {
|
||||
var
|
||||
i,
|
||||
n = this.length,
|
||||
// For GC efficiency, pass a single c_value object to the mapper.
|
||||
c_value = {}
|
||||
|
||||
for (i = 0; i < n; i++) {
|
||||
c_value.real = this.real[i]
|
||||
c_value.imag = this.imag[i]
|
||||
mapper(c_value, i, n)
|
||||
this.real[i] = c_value.real
|
||||
this.imag[i] = c_value.imag
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
ComplexArray.prototype.forEach = function(iterator) {
|
||||
var
|
||||
i,
|
||||
n = this.length,
|
||||
// For consistency with .map.
|
||||
c_value = {}
|
||||
|
||||
for (i = 0; i < n; i++) {
|
||||
c_value.real = this.real[i]
|
||||
c_value.imag = this.imag[i]
|
||||
iterator(c_value, i, n)
|
||||
}
|
||||
}
|
||||
|
||||
ComplexArray.prototype.conjugate = function() {
|
||||
return (new ComplexArray(this)).map(function(value) {
|
||||
value.imag *= -1
|
||||
})
|
||||
}
|
||||
|
||||
// Helper so we can make ArrayType objects returned have similar interfaces
|
||||
// to ComplexArrays.
|
||||
function iterable(obj) {
|
||||
if (!obj.forEach)
|
||||
obj.forEach = function(iterator) {
|
||||
var i, n = this.length
|
||||
|
||||
for (i = 0; i < n; i++)
|
||||
iterator(this[i], i, n)
|
||||
}
|
||||
|
||||
return obj
|
||||
}
|
||||
|
||||
ComplexArray.prototype.magnitude = function() {
|
||||
var mags = new this.ArrayType(this.length)
|
||||
|
||||
this.forEach(function(value, i) {
|
||||
mags[i] = sqrt(sqr(value.real) + sqr(value.imag))
|
||||
})
|
||||
|
||||
// ArrayType will not necessarily be iterable: make it so.
|
||||
return iterable(mags)
|
||||
}
|
||||
}(typeof exports === 'undefined' && (this.complex_array = {}) || exports)
|
225
sensortoy/sensortoy/app/libs/jsfft/fft.js
Normal file
225
sensortoy/sensortoy/app/libs/jsfft/fft.js
Normal file
@ -0,0 +1,225 @@
|
||||
'use strict';
|
||||
|
||||
!function(exports, complex_array) {
|
||||
|
||||
var
|
||||
ComplexArray = complex_array.ComplexArray,
|
||||
// Math constants and functions we need.
|
||||
PI = Math.PI,
|
||||
SQRT1_2 = Math.SQRT1_2,
|
||||
sqrt = Math.sqrt,
|
||||
cos = Math.cos,
|
||||
sin = Math.sin
|
||||
|
||||
ComplexArray.prototype.FFT = function() {
|
||||
return FFT(this, false)
|
||||
}
|
||||
|
||||
exports.FFT = function(input) {
|
||||
return ensureComplexArray(input).FFT()
|
||||
}
|
||||
|
||||
ComplexArray.prototype.InvFFT = function() {
|
||||
return FFT(this, true)
|
||||
}
|
||||
|
||||
exports.InvFFT = function(input) {
|
||||
return ensureComplexArray(input).InvFFT()
|
||||
}
|
||||
|
||||
// Applies a frequency-space filter to input, and returns the real-space
|
||||
// filtered input.
|
||||
// filterer accepts freq, i, n and modifies freq.real and freq.imag.
|
||||
ComplexArray.prototype.frequencyMap = function(filterer) {
|
||||
return this.FFT().map(filterer).InvFFT()
|
||||
}
|
||||
|
||||
exports.frequencyMap = function(input, filterer) {
|
||||
return ensureComplexArray(input).frequencyMap(filterer)
|
||||
}
|
||||
|
||||
function ensureComplexArray(input) {
|
||||
return complex_array.isComplexArray(input) && input ||
|
||||
new ComplexArray(input)
|
||||
}
|
||||
|
||||
function FFT(input, inverse) {
|
||||
var n = input.length
|
||||
|
||||
if (n & (n - 1)) {
|
||||
return FFT_Recursive(input, inverse)
|
||||
} else {
|
||||
return FFT_2_Iterative(input, inverse)
|
||||
}
|
||||
}
|
||||
|
||||
function FFT_Recursive(input, inverse) {
|
||||
var
|
||||
n = input.length,
|
||||
// Counters.
|
||||
i, j,
|
||||
output,
|
||||
// Complex multiplier and its delta.
|
||||
f_r, f_i, del_f_r, del_f_i,
|
||||
// Lowest divisor and remainder.
|
||||
p, m,
|
||||
normalisation,
|
||||
recursive_result,
|
||||
_swap, _real, _imag
|
||||
|
||||
if (n === 1) {
|
||||
return input
|
||||
}
|
||||
|
||||
output = new ComplexArray(n, input.ArrayType)
|
||||
|
||||
// Use the lowest odd factor, so we are able to use FFT_2_Iterative in the
|
||||
// recursive transforms optimally.
|
||||
p = LowestOddFactor(n)
|
||||
m = n / p
|
||||
normalisation = 1 / sqrt(p)
|
||||
recursive_result = new ComplexArray(m, input.ArrayType)
|
||||
|
||||
// Loops go like O(n Σ p_i), where p_i are the prime factors of n.
|
||||
// for a power of a prime, p, this reduces to O(n p log_p n)
|
||||
for(j = 0; j < p; j++) {
|
||||
for(i = 0; i < m; i++) {
|
||||
recursive_result.real[i] = input.real[i * p + j]
|
||||
recursive_result.imag[i] = input.imag[i * p + j]
|
||||
}
|
||||
// Don't go deeper unless necessary to save allocs.
|
||||
if (m > 1) {
|
||||
recursive_result = FFT(recursive_result, inverse)
|
||||
}
|
||||
|
||||
del_f_r = cos(2*PI*j/n)
|
||||
del_f_i = (inverse ? -1 : 1) * sin(2*PI*j/n)
|
||||
f_r = 1
|
||||
f_i = 0
|
||||
|
||||
for(i = 0; i < n; i++) {
|
||||
_real = recursive_result.real[i % m]
|
||||
_imag = recursive_result.imag[i % m]
|
||||
|
||||
output.real[i] += f_r * _real - f_i * _imag
|
||||
output.imag[i] += f_r * _imag + f_i * _real
|
||||
|
||||
_swap = f_r * del_f_r - f_i * del_f_i
|
||||
f_i = f_r * del_f_i + f_i * del_f_r
|
||||
f_r = _swap
|
||||
}
|
||||
}
|
||||
|
||||
// Copy back to input to match FFT_2_Iterative in-placeness
|
||||
// TODO: faster way of making this in-place?
|
||||
for(i = 0; i < n; i++) {
|
||||
input.real[i] = normalisation * output.real[i]
|
||||
input.imag[i] = normalisation * output.imag[i]
|
||||
}
|
||||
|
||||
return input
|
||||
}
|
||||
|
||||
function FFT_2_Iterative(input, inverse) {
|
||||
var
|
||||
n = input.length,
|
||||
// Counters.
|
||||
i, j,
|
||||
output, output_r, output_i,
|
||||
// Complex multiplier and its delta.
|
||||
f_r, f_i, del_f_r, del_f_i, temp,
|
||||
// Temporary loop variables.
|
||||
l_index, r_index,
|
||||
left_r, left_i, right_r, right_i,
|
||||
// width of each sub-array for which we're iteratively calculating FFT.
|
||||
width
|
||||
|
||||
output = BitReverseComplexArray(input)
|
||||
output_r = output.real
|
||||
output_i = output.imag
|
||||
// Loops go like O(n log n):
|
||||
// width ~ log n; i,j ~ n
|
||||
width = 1
|
||||
while (width < n) {
|
||||
del_f_r = cos(PI/width)
|
||||
del_f_i = (inverse ? -1 : 1) * sin(PI/width)
|
||||
for (i = 0; i < n/(2*width); i++) {
|
||||
f_r = 1
|
||||
f_i = 0
|
||||
for (j = 0; j < width; j++) {
|
||||
l_index = 2*i*width + j
|
||||
r_index = l_index + width
|
||||
|
||||
left_r = output_r[l_index]
|
||||
left_i = output_i[l_index]
|
||||
right_r = f_r * output_r[r_index] - f_i * output_i[r_index]
|
||||
right_i = f_i * output_r[r_index] + f_r * output_i[r_index]
|
||||
|
||||
output_r[l_index] = SQRT1_2 * (left_r + right_r)
|
||||
output_i[l_index] = SQRT1_2 * (left_i + right_i)
|
||||
output_r[r_index] = SQRT1_2 * (left_r - right_r)
|
||||
output_i[r_index] = SQRT1_2 * (left_i - right_i)
|
||||
temp = f_r * del_f_r - f_i * del_f_i
|
||||
f_i = f_r * del_f_i + f_i * del_f_r
|
||||
f_r = temp
|
||||
}
|
||||
}
|
||||
width <<= 1
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
function BitReverseIndex(index, n) {
|
||||
var bitreversed_index = 0
|
||||
|
||||
while (n > 1) {
|
||||
bitreversed_index <<= 1
|
||||
bitreversed_index += index & 1
|
||||
index >>= 1
|
||||
n >>= 1
|
||||
}
|
||||
return bitreversed_index
|
||||
}
|
||||
|
||||
function BitReverseComplexArray(array) {
|
||||
var n = array.length,
|
||||
flips = {},
|
||||
swap,
|
||||
i
|
||||
|
||||
for(i = 0; i < n; i++) {
|
||||
var r_i = BitReverseIndex(i, n)
|
||||
|
||||
if (flips.hasOwnProperty(i) || flips.hasOwnProperty(r_i)) continue
|
||||
|
||||
swap = array.real[r_i]
|
||||
array.real[r_i] = array.real[i]
|
||||
array.real[i] = swap
|
||||
|
||||
swap = array.imag[r_i]
|
||||
array.imag[r_i] = array.imag[i]
|
||||
array.imag[i] = swap
|
||||
|
||||
flips[i] = flips[r_i] = true
|
||||
}
|
||||
|
||||
return array
|
||||
}
|
||||
|
||||
function LowestOddFactor(n) {
|
||||
var factor = 3,
|
||||
sqrt_n = sqrt(n)
|
||||
|
||||
while(factor <= sqrt_n) {
|
||||
if (n % factor === 0) return factor
|
||||
factor = factor + 2
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
}(
|
||||
typeof exports === 'undefined' && (this.fft = {}) || exports,
|
||||
typeof require === 'undefined' && (this.complex_array) ||
|
||||
require('./complex_array')
|
||||
)
|
87
sensortoy/sensortoy/app/libs/jsfft/fft_image.js
Normal file
87
sensortoy/sensortoy/app/libs/jsfft/fft_image.js
Normal file
@ -0,0 +1,87 @@
|
||||
'use strict';
|
||||
|
||||
!function(complex_array, fft) {
|
||||
|
||||
var ComplexArray = complex_array.ComplexArray
|
||||
|
||||
fft.FFTImageDataRGBA = function(data, nx, ny) {
|
||||
var rgb = splitRGB(data)
|
||||
|
||||
return mergeRGB(
|
||||
FFT2D(new ComplexArray(rgb[0], Float32Array), nx, ny),
|
||||
FFT2D(new ComplexArray(rgb[1], Float32Array), nx, ny),
|
||||
FFT2D(new ComplexArray(rgb[2], Float32Array), nx, ny)
|
||||
)
|
||||
}
|
||||
|
||||
function splitRGB(data) {
|
||||
var n = data.length / 4,
|
||||
r = new Uint8ClampedArray(n),
|
||||
g = new Uint8ClampedArray(n),
|
||||
b = new Uint8ClampedArray(n),
|
||||
i
|
||||
|
||||
for(i = 0; i < n; i++) {
|
||||
r[i] = data[4 * i ]
|
||||
g[i] = data[4 * i + 1]
|
||||
b[i] = data[4 * i + 2]
|
||||
}
|
||||
|
||||
return [r, g, b]
|
||||
}
|
||||
|
||||
function mergeRGB(r, g, b) {
|
||||
var n = r.length,
|
||||
output = new ComplexArray(n * 4),
|
||||
i
|
||||
|
||||
for(i = 0; i < n; i++) {
|
||||
output.real[4 * i ] = r.real[i]
|
||||
output.imag[4 * i ] = r.imag[i]
|
||||
output.real[4 * i + 1] = g.real[i]
|
||||
output.imag[4 * i + 1] = g.imag[i]
|
||||
output.real[4 * i + 2] = b.real[i]
|
||||
output.imag[4 * i + 2] = b.imag[i]
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
function FFT2D(input, nx, ny, inverse) {
|
||||
var i, j,
|
||||
transform = inverse ? 'InvFFT' : 'FFT',
|
||||
output = new ComplexArray(input.length, input.ArrayType),
|
||||
row = new ComplexArray(nx, input.ArrayType),
|
||||
col = new ComplexArray(ny, input.ArrayType)
|
||||
|
||||
for(j = 0; j < ny; j++) {
|
||||
row.map(function(v, i) {
|
||||
v.real = input.real[i + j * nx]
|
||||
v.imag = input.imag[i + j * nx]
|
||||
})
|
||||
row[transform]().forEach(function(v, i) {
|
||||
output.real[i + j * nx] = v.real
|
||||
output.imag[i + j * nx] = v.imag
|
||||
})
|
||||
}
|
||||
|
||||
for(i = 0; i < nx; i++) {
|
||||
col.map(function(v, j) {
|
||||
v.real = output.real[i + j * nx]
|
||||
v.imag = output.imag[i + j * nx]
|
||||
})
|
||||
col[transform]().forEach(function(v, j) {
|
||||
output.real[i + j * nx] = v.real
|
||||
output.imag[i + j * nx] = v.imag
|
||||
})
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
}(
|
||||
typeof require === 'undefined' && (this.complex_array) ||
|
||||
require('./complex_array'),
|
||||
typeof require === 'undefined' && (this.fft) ||
|
||||
require('./fft')
|
||||
)
|
803
sensortoy/sensortoy/app/libs/smoothie.js
Normal file
803
sensortoy/sensortoy/app/libs/smoothie.js
Normal file
@ -0,0 +1,803 @@
|
||||
// MIT License:
|
||||
//
|
||||
// Copyright (c) 2010-2013, Joe Walnes
|
||||
// 2013-2014, Drew Noakes
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
/**
|
||||
* Smoothie Charts - http://smoothiecharts.org/
|
||||
* (c) 2010-2013, Joe Walnes
|
||||
* 2013-2014, Drew Noakes
|
||||
*
|
||||
* v1.0: Main charting library, by Joe Walnes
|
||||
* v1.1: Auto scaling of axis, by Neil Dunn
|
||||
* v1.2: fps (frames per second) option, by Mathias Petterson
|
||||
* v1.3: Fix for divide by zero, by Paul Nikitochkin
|
||||
* v1.4: Set minimum, top-scale padding, remove timeseries, add optional timer to reset bounds, by Kelley Reynolds
|
||||
* v1.5: Set default frames per second to 50... smoother.
|
||||
* .start(), .stop() methods for conserving CPU, by Dmitry Vyal
|
||||
* options.interpolation = 'bezier' or 'line', by Dmitry Vyal
|
||||
* options.maxValue to fix scale, by Dmitry Vyal
|
||||
* v1.6: minValue/maxValue will always get converted to floats, by Przemek Matylla
|
||||
* v1.7: options.grid.fillStyle may be a transparent color, by Dmitry A. Shashkin
|
||||
* Smooth rescaling, by Kostas Michalopoulos
|
||||
* v1.8: Set max length to customize number of live points in the dataset with options.maxDataSetLength, by Krishna Narni
|
||||
* v1.9: Display timestamps along the bottom, by Nick and Stev-io
|
||||
* (https://groups.google.com/forum/?fromgroups#!topic/smoothie-charts/-Ywse8FCpKI%5B1-25%5D)
|
||||
* Refactored by Krishna Narni, to support timestamp formatting function
|
||||
* v1.10: Switch to requestAnimationFrame, removed the now obsoleted options.fps, by Gergely Imreh
|
||||
* v1.11: options.grid.sharpLines option added, by @drewnoakes
|
||||
* Addressed warning seen in Firefox when seriesOption.fillStyle undefined, by @drewnoakes
|
||||
* v1.12: Support for horizontalLines added, by @drewnoakes
|
||||
* Support for yRangeFunction callback added, by @drewnoakes
|
||||
* v1.13: Fixed typo (#32), by @alnikitich
|
||||
* v1.14: Timer cleared when last TimeSeries removed (#23), by @davidgaleano
|
||||
* Fixed diagonal line on chart at start/end of data stream, by @drewnoakes
|
||||
* v1.15: Support for npm package (#18), by @dominictarr
|
||||
* Fixed broken removeTimeSeries function (#24) by @davidgaleano
|
||||
* Minor performance and tidying, by @drewnoakes
|
||||
* v1.16: Bug fix introduced in v1.14 relating to timer creation/clearance (#23), by @drewnoakes
|
||||
* TimeSeries.append now deals with out-of-order timestamps, and can merge duplicates, by @zacwitte (#12)
|
||||
* Documentation and some local variable renaming for clarity, by @drewnoakes
|
||||
* v1.17: Allow control over font size (#10), by @drewnoakes
|
||||
* Timestamp text won't overlap, by @drewnoakes
|
||||
* v1.18: Allow control of max/min label precision, by @drewnoakes
|
||||
* Added 'borderVisible' chart option, by @drewnoakes
|
||||
* Allow drawing series with fill but no stroke (line), by @drewnoakes
|
||||
* v1.19: Avoid unnecessary repaints, and fixed flicker in old browsers having multiple charts in document (#40), by @asbai
|
||||
* v1.20: Add SmoothieChart.getTimeSeriesOptions and SmoothieChart.bringToFront functions, by @drewnoakes
|
||||
* v1.21: Add 'step' interpolation mode, by @drewnoakes
|
||||
* v1.22: Add support for different pixel ratios. Also add optional y limit formatters, by @copacetic
|
||||
* v1.23: Fix bug introduced in v1.22 (#44), by @drewnoakes
|
||||
* v1.24: Fix bug introduced in v1.23, re-adding parseFloat to y-axis formatter defaults, by @siggy_sf
|
||||
* v1.25: Fix bug seen when adding a data point to TimeSeries which is older than the current data, by @Nking92
|
||||
* Draw time labels on top of series, by @comolosabia
|
||||
* Add TimeSeries.clear function, by @drewnoakes
|
||||
* v1.26: Add support for resizing on high device pixel ratio screens, by @copacetic
|
||||
* v1.27: Fix bug introduced in v1.26 for non whole number devicePixelRatio values, by @zmbush
|
||||
* v1.28: Add 'minValueScale' option, by @megawac
|
||||
*/
|
||||
|
||||
;(function(exports) {
|
||||
|
||||
var Util = {
|
||||
extend: function() {
|
||||
arguments[0] = arguments[0] || {};
|
||||
for (var i = 1; i < arguments.length; i++)
|
||||
{
|
||||
for (var key in arguments[i])
|
||||
{
|
||||
if (arguments[i].hasOwnProperty(key))
|
||||
{
|
||||
if (typeof(arguments[i][key]) === 'object') {
|
||||
if (arguments[i][key] instanceof Array) {
|
||||
arguments[0][key] = arguments[i][key];
|
||||
} else {
|
||||
arguments[0][key] = Util.extend(arguments[0][key], arguments[i][key]);
|
||||
}
|
||||
} else {
|
||||
arguments[0][key] = arguments[i][key];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return arguments[0];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialises a new <code>TimeSeries</code> with optional data options.
|
||||
*
|
||||
* Options are of the form (defaults shown):
|
||||
*
|
||||
* <pre>
|
||||
* {
|
||||
* resetBounds: true, // enables/disables automatic scaling of the y-axis
|
||||
* resetBoundsInterval: 3000 // the period between scaling calculations, in millis
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* Presentation options for TimeSeries are specified as an argument to <code>SmoothieChart.addTimeSeries</code>.
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
function TimeSeries(options) {
|
||||
this.options = Util.extend({}, TimeSeries.defaultOptions, options);
|
||||
this.clear();
|
||||
}
|
||||
|
||||
TimeSeries.defaultOptions = {
|
||||
resetBoundsInterval: 3000,
|
||||
resetBounds: true
|
||||
};
|
||||
|
||||
/**
|
||||
* Clears all data and state from this TimeSeries object.
|
||||
*/
|
||||
TimeSeries.prototype.clear = function() {
|
||||
this.data = [];
|
||||
this.maxValue = Number.NaN; // The maximum value ever seen in this TimeSeries.
|
||||
this.minValue = Number.NaN; // The minimum value ever seen in this TimeSeries.
|
||||
};
|
||||
|
||||
/**
|
||||
* Recalculate the min/max values for this <code>TimeSeries</code> object.
|
||||
*
|
||||
* This causes the graph to scale itself in the y-axis.
|
||||
*/
|
||||
TimeSeries.prototype.resetBounds = function() {
|
||||
if (this.data.length) {
|
||||
// Walk through all data points, finding the min/max value
|
||||
this.maxValue = this.data[0][1];
|
||||
this.minValue = this.data[0][1];
|
||||
for (var i = 1; i < this.data.length; i++) {
|
||||
var value = this.data[i][1];
|
||||
if (value > this.maxValue) {
|
||||
this.maxValue = value;
|
||||
}
|
||||
if (value < this.minValue) {
|
||||
this.minValue = value;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No data exists, so set min/max to NaN
|
||||
this.maxValue = Number.NaN;
|
||||
this.minValue = Number.NaN;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a new data point to the <code>TimeSeries</code>, preserving chronological order.
|
||||
*
|
||||
* @param timestamp the position, in time, of this data point
|
||||
* @param value the value of this data point
|
||||
* @param sumRepeatedTimeStampValues if <code>timestamp</code> has an exact match in the series, this flag controls
|
||||
* whether it is replaced, or the values summed (defaults to false.)
|
||||
*/
|
||||
TimeSeries.prototype.append = function(timestamp, value, sumRepeatedTimeStampValues) {
|
||||
// Rewind until we hit an older timestamp
|
||||
var i = this.data.length - 1;
|
||||
while (i >= 0 && this.data[i][0] > timestamp) {
|
||||
i--;
|
||||
}
|
||||
|
||||
if (i === -1) {
|
||||
// This new item is the oldest data
|
||||
this.data.splice(0, 0, [timestamp, value]);
|
||||
} else if (this.data.length > 0 && this.data[i][0] === timestamp) {
|
||||
// Update existing values in the array
|
||||
if (sumRepeatedTimeStampValues) {
|
||||
// Sum this value into the existing 'bucket'
|
||||
this.data[i][1] += value;
|
||||
value = this.data[i][1];
|
||||
} else {
|
||||
// Replace the previous value
|
||||
this.data[i][1] = value;
|
||||
}
|
||||
} else if (i < this.data.length - 1) {
|
||||
// Splice into the correct position to keep timestamps in order
|
||||
this.data.splice(i + 1, 0, [timestamp, value]);
|
||||
} else {
|
||||
// Add to the end of the array
|
||||
this.data.push([timestamp, value]);
|
||||
}
|
||||
|
||||
this.maxValue = isNaN(this.maxValue) ? value : Math.max(this.maxValue, value);
|
||||
this.minValue = isNaN(this.minValue) ? value : Math.min(this.minValue, value);
|
||||
};
|
||||
|
||||
TimeSeries.prototype.dropOldData = function(oldestValidTime, maxDataSetLength) {
|
||||
// We must always keep one expired data point as we need this to draw the
|
||||
// line that comes into the chart from the left, but any points prior to that can be removed.
|
||||
var removeCount = 0;
|
||||
while (this.data.length - removeCount >= maxDataSetLength && this.data[removeCount + 1][0] < oldestValidTime) {
|
||||
removeCount++;
|
||||
}
|
||||
if (removeCount !== 0) {
|
||||
this.data.splice(0, removeCount);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialises a new <code>SmoothieChart</code>.
|
||||
*
|
||||
* Options are optional, and should be of the form below. Just specify the values you
|
||||
* need and the rest will be given sensible defaults as shown:
|
||||
*
|
||||
* <pre>
|
||||
* {
|
||||
* minValue: undefined, // specify to clamp the lower y-axis to a given value
|
||||
* maxValue: undefined, // specify to clamp the upper y-axis to a given value
|
||||
* maxValueScale: 1, // allows proportional padding to be added above the chart. for 10% padding, specify 1.1.
|
||||
* minValueScale: 1, // allows proportional padding to be added below the chart. for 10% padding, specify 1.1.
|
||||
* yRangeFunction: undefined, // function({min: , max: }) { return {min: , max: }; }
|
||||
* scaleSmoothing: 0.125, // controls the rate at which y-value zoom animation occurs
|
||||
* millisPerPixel: 20, // sets the speed at which the chart pans by
|
||||
* enableDpiScaling: true, // support rendering at different DPI depending on the device
|
||||
* yMinFormatter: function(min, precision) { // callback function that formats the min y value label
|
||||
* return parseFloat(min).toFixed(precision);
|
||||
* },
|
||||
* yMaxFormatter: function(max, precision) { // callback function that formats the max y value label
|
||||
* return parseFloat(max).toFixed(precision);
|
||||
* },
|
||||
* maxDataSetLength: 2,
|
||||
* interpolation: 'bezier' // one of 'bezier', 'linear', or 'step'
|
||||
* timestampFormatter: null, // optional function to format time stamps for bottom of chart
|
||||
* // you may use SmoothieChart.timeFormatter, or your own: function(date) { return ''; }
|
||||
* scrollBackwards: false, // reverse the scroll direction of the chart
|
||||
* horizontalLines: [], // [ { value: 0, color: '#ffffff', lineWidth: 1 } ]
|
||||
* grid:
|
||||
* {
|
||||
* fillStyle: '#000000', // the background colour of the chart
|
||||
* lineWidth: 1, // the pixel width of grid lines
|
||||
* strokeStyle: '#777777', // colour of grid lines
|
||||
* millisPerLine: 1000, // distance between vertical grid lines
|
||||
* sharpLines: false, // controls whether grid lines are 1px sharp, or softened
|
||||
* verticalSections: 2, // number of vertical sections marked out by horizontal grid lines
|
||||
* borderVisible: true // whether the grid lines trace the border of the chart or not
|
||||
* },
|
||||
* labels
|
||||
* {
|
||||
* disabled: false, // enables/disables labels showing the min/max values
|
||||
* fillStyle: '#ffffff', // colour for text of labels,
|
||||
* fontSize: 15,
|
||||
* fontFamily: 'sans-serif',
|
||||
* precision: 2
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
function SmoothieChart(options) {
|
||||
this.options = Util.extend({}, SmoothieChart.defaultChartOptions, options);
|
||||
this.seriesSet = [];
|
||||
this.currentValueRange = 1;
|
||||
this.currentVisMinValue = 0;
|
||||
this.lastRenderTimeMillis = 0;
|
||||
}
|
||||
|
||||
SmoothieChart.defaultChartOptions = {
|
||||
millisPerPixel: 20,
|
||||
enableDpiScaling: true,
|
||||
yMinFormatter: function(min, precision) {
|
||||
return parseFloat(min).toFixed(precision);
|
||||
},
|
||||
yMaxFormatter: function(max, precision) {
|
||||
return parseFloat(max).toFixed(precision);
|
||||
},
|
||||
maxValueScale: 1,
|
||||
minValueScale: 1,
|
||||
interpolation: 'bezier',
|
||||
scaleSmoothing: 0.125,
|
||||
maxDataSetLength: 2,
|
||||
scrollBackwards: false,
|
||||
grid: {
|
||||
fillStyle: '#000000',
|
||||
strokeStyle: '#777777',
|
||||
lineWidth: 1,
|
||||
sharpLines: false,
|
||||
millisPerLine: 1000,
|
||||
verticalSections: 2,
|
||||
borderVisible: true
|
||||
},
|
||||
labels: {
|
||||
fillStyle: '#ffffff',
|
||||
disabled: false,
|
||||
fontSize: 10,
|
||||
fontFamily: 'monospace',
|
||||
precision: 2
|
||||
},
|
||||
horizontalLines: []
|
||||
};
|
||||
|
||||
// Based on http://inspirit.github.com/jsfeat/js/compatibility.js
|
||||
SmoothieChart.AnimateCompatibility = (function() {
|
||||
var requestAnimationFrame = function(callback, element) {
|
||||
var requestAnimationFrame =
|
||||
window.requestAnimationFrame ||
|
||||
window.webkitRequestAnimationFrame ||
|
||||
window.mozRequestAnimationFrame ||
|
||||
window.oRequestAnimationFrame ||
|
||||
window.msRequestAnimationFrame ||
|
||||
function(callback) {
|
||||
return window.setTimeout(function() {
|
||||
callback(new Date().getTime());
|
||||
}, 16);
|
||||
};
|
||||
return requestAnimationFrame.call(window, callback, element);
|
||||
},
|
||||
cancelAnimationFrame = function(id) {
|
||||
var cancelAnimationFrame =
|
||||
window.cancelAnimationFrame ||
|
||||
function(id) {
|
||||
clearTimeout(id);
|
||||
};
|
||||
return cancelAnimationFrame.call(window, id);
|
||||
};
|
||||
|
||||
return {
|
||||
requestAnimationFrame: requestAnimationFrame,
|
||||
cancelAnimationFrame: cancelAnimationFrame
|
||||
};
|
||||
})();
|
||||
|
||||
SmoothieChart.defaultSeriesPresentationOptions = {
|
||||
lineWidth: 1,
|
||||
strokeStyle: '#ffffff'
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a <code>TimeSeries</code> to this chart, with optional presentation options.
|
||||
*
|
||||
* Presentation options should be of the form (defaults shown):
|
||||
*
|
||||
* <pre>
|
||||
* {
|
||||
* lineWidth: 1,
|
||||
* strokeStyle: '#ffffff',
|
||||
* fillStyle: undefined
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
SmoothieChart.prototype.addTimeSeries = function(timeSeries, options) {
|
||||
this.seriesSet.push({timeSeries: timeSeries, options: Util.extend({}, SmoothieChart.defaultSeriesPresentationOptions, options)});
|
||||
if (timeSeries.options.resetBounds && timeSeries.options.resetBoundsInterval > 0) {
|
||||
timeSeries.resetBoundsTimerId = setInterval(
|
||||
function() {
|
||||
timeSeries.resetBounds();
|
||||
},
|
||||
timeSeries.options.resetBoundsInterval
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes the specified <code>TimeSeries</code> from the chart.
|
||||
*/
|
||||
SmoothieChart.prototype.removeTimeSeries = function(timeSeries) {
|
||||
// Find the correct timeseries to remove, and remove it
|
||||
var numSeries = this.seriesSet.length;
|
||||
for (var i = 0; i < numSeries; i++) {
|
||||
if (this.seriesSet[i].timeSeries === timeSeries) {
|
||||
this.seriesSet.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// If a timer was operating for that timeseries, remove it
|
||||
if (timeSeries.resetBoundsTimerId) {
|
||||
// Stop resetting the bounds, if we were
|
||||
clearInterval(timeSeries.resetBoundsTimerId);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets render options for the specified <code>TimeSeries</code>.
|
||||
*
|
||||
* As you may use a single <code>TimeSeries</code> in multiple charts with different formatting in each usage,
|
||||
* these settings are stored in the chart.
|
||||
*/
|
||||
SmoothieChart.prototype.getTimeSeriesOptions = function(timeSeries) {
|
||||
// Find the correct timeseries to remove, and remove it
|
||||
var numSeries = this.seriesSet.length;
|
||||
for (var i = 0; i < numSeries; i++) {
|
||||
if (this.seriesSet[i].timeSeries === timeSeries) {
|
||||
return this.seriesSet[i].options;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Brings the specified <code>TimeSeries</code> to the top of the chart. It will be rendered last.
|
||||
*/
|
||||
SmoothieChart.prototype.bringToFront = function(timeSeries) {
|
||||
// Find the correct timeseries to remove, and remove it
|
||||
var numSeries = this.seriesSet.length;
|
||||
for (var i = 0; i < numSeries; i++) {
|
||||
if (this.seriesSet[i].timeSeries === timeSeries) {
|
||||
var set = this.seriesSet.splice(i, 1);
|
||||
this.seriesSet.push(set[0]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Instructs the <code>SmoothieChart</code> to start rendering to the provided canvas, with specified delay.
|
||||
*
|
||||
* @param canvas the target canvas element
|
||||
* @param delayMillis an amount of time to wait before a data point is shown. This can prevent the end of the series
|
||||
* from appearing on screen, with new values flashing into view, at the expense of some latency.
|
||||
*/
|
||||
SmoothieChart.prototype.streamTo = function(canvas, delayMillis) {
|
||||
this.canvas = canvas;
|
||||
this.delay = delayMillis;
|
||||
this.start();
|
||||
};
|
||||
|
||||
/**
|
||||
* Make sure the canvas has the optimal resolution for the device's pixel ratio.
|
||||
*/
|
||||
SmoothieChart.prototype.resize = function() {
|
||||
// TODO this function doesn't handle the value of enableDpiScaling changing during execution
|
||||
if (!this.options.enableDpiScaling || !window || window.devicePixelRatio === 1)
|
||||
return;
|
||||
|
||||
var dpr = window.devicePixelRatio;
|
||||
var width = parseInt(this.canvas.getAttribute('width'));
|
||||
var height = parseInt(this.canvas.getAttribute('height'));
|
||||
|
||||
if (!this.originalWidth || (Math.floor(this.originalWidth * dpr) !== width)) {
|
||||
this.originalWidth = width;
|
||||
this.canvas.setAttribute('width', (Math.floor(width * dpr)).toString());
|
||||
this.canvas.style.width = width + 'px';
|
||||
this.canvas.getContext('2d').scale(dpr, dpr);
|
||||
}
|
||||
|
||||
if (!this.originalHeight || (Math.floor(this.originalHeight * dpr) !== height)) {
|
||||
this.originalHeight = height;
|
||||
this.canvas.setAttribute('height', (Math.floor(height * dpr)).toString());
|
||||
this.canvas.style.height = height + 'px';
|
||||
this.canvas.getContext('2d').scale(dpr, dpr);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Starts the animation of this chart.
|
||||
*/
|
||||
SmoothieChart.prototype.start = function() {
|
||||
if (this.frame) {
|
||||
// We're already running, so just return
|
||||
return;
|
||||
}
|
||||
|
||||
// Renders a frame, and queues the next frame for later rendering
|
||||
var animate = function() {
|
||||
this.frame = SmoothieChart.AnimateCompatibility.requestAnimationFrame(function() {
|
||||
this.render();
|
||||
animate();
|
||||
}.bind(this));
|
||||
}.bind(this);
|
||||
|
||||
animate();
|
||||
};
|
||||
|
||||
/**
|
||||
* Stops the animation of this chart.
|
||||
*/
|
||||
SmoothieChart.prototype.stop = function() {
|
||||
if (this.frame) {
|
||||
SmoothieChart.AnimateCompatibility.cancelAnimationFrame(this.frame);
|
||||
delete this.frame;
|
||||
}
|
||||
};
|
||||
|
||||
SmoothieChart.prototype.updateValueRange = function() {
|
||||
// Calculate the current scale of the chart, from all time series.
|
||||
var chartOptions = this.options,
|
||||
chartMaxValue = Number.NaN,
|
||||
chartMinValue = Number.NaN;
|
||||
|
||||
for (var d = 0; d < this.seriesSet.length; d++) {
|
||||
// TODO(ndunn): We could calculate / track these values as they stream in.
|
||||
var timeSeries = this.seriesSet[d].timeSeries;
|
||||
if (!isNaN(timeSeries.maxValue)) {
|
||||
chartMaxValue = !isNaN(chartMaxValue) ? Math.max(chartMaxValue, timeSeries.maxValue) : timeSeries.maxValue;
|
||||
}
|
||||
|
||||
if (!isNaN(timeSeries.minValue)) {
|
||||
chartMinValue = !isNaN(chartMinValue) ? Math.min(chartMinValue, timeSeries.minValue) : timeSeries.minValue;
|
||||
}
|
||||
}
|
||||
|
||||
// Scale the chartMaxValue to add padding at the top if required
|
||||
if (chartOptions.maxValue != null) {
|
||||
chartMaxValue = chartOptions.maxValue;
|
||||
} else {
|
||||
chartMaxValue *= chartOptions.maxValueScale;
|
||||
}
|
||||
|
||||
// Set the minimum if we've specified one
|
||||
if (chartOptions.minValue != null) {
|
||||
chartMinValue = chartOptions.minValue;
|
||||
} else {
|
||||
chartMinValue -= Math.abs(chartMinValue * chartOptions.minValueScale - chartMinValue);
|
||||
}
|
||||
|
||||
// If a custom range function is set, call it
|
||||
if (this.options.yRangeFunction) {
|
||||
var range = this.options.yRangeFunction({min: chartMinValue, max: chartMaxValue});
|
||||
chartMinValue = range.min;
|
||||
chartMaxValue = range.max;
|
||||
}
|
||||
|
||||
if (!isNaN(chartMaxValue) && !isNaN(chartMinValue)) {
|
||||
var targetValueRange = chartMaxValue - chartMinValue;
|
||||
var valueRangeDiff = (targetValueRange - this.currentValueRange);
|
||||
var minValueDiff = (chartMinValue - this.currentVisMinValue);
|
||||
this.isAnimatingScale = Math.abs(valueRangeDiff) > 0.1 || Math.abs(minValueDiff) > 0.1;
|
||||
this.currentValueRange += chartOptions.scaleSmoothing * valueRangeDiff;
|
||||
this.currentVisMinValue += chartOptions.scaleSmoothing * minValueDiff;
|
||||
}
|
||||
|
||||
this.valueRange = { min: chartMinValue, max: chartMaxValue };
|
||||
};
|
||||
|
||||
SmoothieChart.prototype.render = function(canvas, time) {
|
||||
var nowMillis = new Date().getTime();
|
||||
|
||||
if (!this.isAnimatingScale) {
|
||||
// We're not animating. We can use the last render time and the scroll speed to work out whether
|
||||
// we actually need to paint anything yet. If not, we can return immediately.
|
||||
|
||||
// Render at least every 1/6th of a second. The canvas may be resized, which there is
|
||||
// no reliable way to detect.
|
||||
var maxIdleMillis = Math.min(1000/6, this.options.millisPerPixel);
|
||||
|
||||
if (nowMillis - this.lastRenderTimeMillis < maxIdleMillis) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.resize();
|
||||
|
||||
this.lastRenderTimeMillis = nowMillis;
|
||||
|
||||
canvas = canvas || this.canvas;
|
||||
time = time || nowMillis - (this.delay || 0);
|
||||
|
||||
// Round time down to pixel granularity, so motion appears smoother.
|
||||
time -= time % this.options.millisPerPixel;
|
||||
|
||||
var context = canvas.getContext('2d'),
|
||||
chartOptions = this.options,
|
||||
dimensions = { top: 0, left: 0, width: canvas.clientWidth, height: canvas.clientHeight },
|
||||
// Calculate the threshold time for the oldest data points.
|
||||
oldestValidTime = time - (dimensions.width * chartOptions.millisPerPixel),
|
||||
valueToYPixel = function(value) {
|
||||
var offset = value - this.currentVisMinValue;
|
||||
return this.currentValueRange === 0
|
||||
? dimensions.height
|
||||
: dimensions.height - (Math.round((offset / this.currentValueRange) * dimensions.height));
|
||||
}.bind(this),
|
||||
timeToXPixel = function(t) {
|
||||
if(chartOptions.scrollBackwards) {
|
||||
return Math.round((time - t) / chartOptions.millisPerPixel);
|
||||
}
|
||||
return Math.round(dimensions.width - ((time - t) / chartOptions.millisPerPixel));
|
||||
};
|
||||
|
||||
this.updateValueRange();
|
||||
|
||||
context.font = chartOptions.labels.fontSize + 'px ' + chartOptions.labels.fontFamily;
|
||||
|
||||
// Save the state of the canvas context, any transformations applied in this method
|
||||
// will get removed from the stack at the end of this method when .restore() is called.
|
||||
context.save();
|
||||
|
||||
// Move the origin.
|
||||
context.translate(dimensions.left, dimensions.top);
|
||||
|
||||
// Create a clipped rectangle - anything we draw will be constrained to this rectangle.
|
||||
// This prevents the occasional pixels from curves near the edges overrunning and creating
|
||||
// screen cheese (that phrase should need no explanation).
|
||||
context.beginPath();
|
||||
context.rect(0, 0, dimensions.width, dimensions.height);
|
||||
context.clip();
|
||||
|
||||
// Clear the working area.
|
||||
context.save();
|
||||
context.fillStyle = chartOptions.grid.fillStyle;
|
||||
context.clearRect(0, 0, dimensions.width, dimensions.height);
|
||||
context.fillRect(0, 0, dimensions.width, dimensions.height);
|
||||
context.restore();
|
||||
|
||||
// Grid lines...
|
||||
context.save();
|
||||
context.lineWidth = chartOptions.grid.lineWidth;
|
||||
context.strokeStyle = chartOptions.grid.strokeStyle;
|
||||
// Vertical (time) dividers.
|
||||
if (chartOptions.grid.millisPerLine > 0) {
|
||||
context.beginPath();
|
||||
for (var t = time - (time % chartOptions.grid.millisPerLine);
|
||||
t >= oldestValidTime;
|
||||
t -= chartOptions.grid.millisPerLine) {
|
||||
var gx = timeToXPixel(t);
|
||||
if (chartOptions.grid.sharpLines) {
|
||||
gx -= 0.5;
|
||||
}
|
||||
context.moveTo(gx, 0);
|
||||
context.lineTo(gx, dimensions.height);
|
||||
}
|
||||
context.stroke();
|
||||
context.closePath();
|
||||
}
|
||||
|
||||
// Horizontal (value) dividers.
|
||||
for (var v = 1; v < chartOptions.grid.verticalSections; v++) {
|
||||
var gy = Math.round(v * dimensions.height / chartOptions.grid.verticalSections);
|
||||
if (chartOptions.grid.sharpLines) {
|
||||
gy -= 0.5;
|
||||
}
|
||||
context.beginPath();
|
||||
context.moveTo(0, gy);
|
||||
context.lineTo(dimensions.width, gy);
|
||||
context.stroke();
|
||||
context.closePath();
|
||||
}
|
||||
// Bounding rectangle.
|
||||
if (chartOptions.grid.borderVisible) {
|
||||
context.beginPath();
|
||||
context.strokeRect(0, 0, dimensions.width, dimensions.height);
|
||||
context.closePath();
|
||||
}
|
||||
context.restore();
|
||||
|
||||
// Draw any horizontal lines...
|
||||
if (chartOptions.horizontalLines && chartOptions.horizontalLines.length) {
|
||||
for (var hl = 0; hl < chartOptions.horizontalLines.length; hl++) {
|
||||
var line = chartOptions.horizontalLines[hl],
|
||||
hly = Math.round(valueToYPixel(line.value)) - 0.5;
|
||||
context.strokeStyle = line.color || '#ffffff';
|
||||
context.lineWidth = line.lineWidth || 1;
|
||||
context.beginPath();
|
||||
context.moveTo(0, hly);
|
||||
context.lineTo(dimensions.width, hly);
|
||||
context.stroke();
|
||||
context.closePath();
|
||||
}
|
||||
}
|
||||
|
||||
// For each data set...
|
||||
for (var d = 0; d < this.seriesSet.length; d++) {
|
||||
context.save();
|
||||
var timeSeries = this.seriesSet[d].timeSeries,
|
||||
dataSet = timeSeries.data,
|
||||
seriesOptions = this.seriesSet[d].options;
|
||||
|
||||
// Delete old data that's moved off the left of the chart.
|
||||
timeSeries.dropOldData(oldestValidTime, chartOptions.maxDataSetLength);
|
||||
|
||||
// Set style for this dataSet.
|
||||
context.lineWidth = seriesOptions.lineWidth;
|
||||
context.strokeStyle = seriesOptions.strokeStyle;
|
||||
// Draw the line...
|
||||
context.beginPath();
|
||||
// Retain lastX, lastY for calculating the control points of bezier curves.
|
||||
var firstX = 0, lastX = 0, lastY = 0;
|
||||
for (var i = 0; i < dataSet.length && dataSet.length !== 1; i++) {
|
||||
var x = timeToXPixel(dataSet[i][0]),
|
||||
y = valueToYPixel(dataSet[i][1]);
|
||||
|
||||
if (i === 0) {
|
||||
firstX = x;
|
||||
context.moveTo(x, y);
|
||||
} else {
|
||||
switch (chartOptions.interpolation) {
|
||||
case "linear":
|
||||
case "line": {
|
||||
context.lineTo(x,y);
|
||||
break;
|
||||
}
|
||||
case "bezier":
|
||||
default: {
|
||||
// Great explanation of Bezier curves: http://en.wikipedia.org/wiki/Bezier_curve#Quadratic_curves
|
||||
//
|
||||
// Assuming A was the last point in the line plotted and B is the new point,
|
||||
// we draw a curve with control points P and Q as below.
|
||||
//
|
||||
// A---P
|
||||
// |
|
||||
// |
|
||||
// |
|
||||
// Q---B
|
||||
//
|
||||
// Importantly, A and P are at the same y coordinate, as are B and Q. This is
|
||||
// so adjacent curves appear to flow as one.
|
||||
//
|
||||
context.bezierCurveTo( // startPoint (A) is implicit from last iteration of loop
|
||||
Math.round((lastX + x) / 2), lastY, // controlPoint1 (P)
|
||||
Math.round((lastX + x)) / 2, y, // controlPoint2 (Q)
|
||||
x, y); // endPoint (B)
|
||||
break;
|
||||
}
|
||||
case "step": {
|
||||
context.lineTo(x,lastY);
|
||||
context.lineTo(x,y);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lastX = x; lastY = y;
|
||||
}
|
||||
|
||||
if (dataSet.length > 1) {
|
||||
if (seriesOptions.fillStyle) {
|
||||
// Close up the fill region.
|
||||
context.lineTo(dimensions.width + seriesOptions.lineWidth + 1, lastY);
|
||||
context.lineTo(dimensions.width + seriesOptions.lineWidth + 1, dimensions.height + seriesOptions.lineWidth + 1);
|
||||
context.lineTo(firstX, dimensions.height + seriesOptions.lineWidth);
|
||||
context.fillStyle = seriesOptions.fillStyle;
|
||||
context.fill();
|
||||
}
|
||||
|
||||
if (seriesOptions.strokeStyle && seriesOptions.strokeStyle !== 'none') {
|
||||
context.stroke();
|
||||
}
|
||||
context.closePath();
|
||||
}
|
||||
context.restore();
|
||||
}
|
||||
|
||||
// Draw the axis values on the chart.
|
||||
if (!chartOptions.labels.disabled && !isNaN(this.valueRange.min) && !isNaN(this.valueRange.max)) {
|
||||
var maxValueString = chartOptions.yMaxFormatter(this.valueRange.max, chartOptions.labels.precision),
|
||||
minValueString = chartOptions.yMinFormatter(this.valueRange.min, chartOptions.labels.precision),
|
||||
labelPos = chartOptions.scrollBackwards ? 0 : dimensions.width - context.measureText(maxValueString).width - 2;
|
||||
context.fillStyle = chartOptions.labels.fillStyle;
|
||||
context.fillText(maxValueString, labelPos, chartOptions.labels.fontSize);
|
||||
context.fillText(minValueString, labelPos, dimensions.height - 2);
|
||||
}
|
||||
|
||||
// Display timestamps along x-axis at the bottom of the chart.
|
||||
if (chartOptions.timestampFormatter && chartOptions.grid.millisPerLine > 0) {
|
||||
var textUntilX = chartOptions.scrollBackwards
|
||||
? context.measureText(minValueString).width
|
||||
: dimensions.width - context.measureText(minValueString).width + 4;
|
||||
for (var t = time - (time % chartOptions.grid.millisPerLine);
|
||||
t >= oldestValidTime;
|
||||
t -= chartOptions.grid.millisPerLine) {
|
||||
var gx = timeToXPixel(t);
|
||||
// Only draw the timestamp if it won't overlap with the previously drawn one.
|
||||
if ((!chartOptions.scrollBackwards && gx < textUntilX) || (chartOptions.scrollBackwards && gx > textUntilX)) {
|
||||
// Formats the timestamp based on user specified formatting function
|
||||
// SmoothieChart.timeFormatter function above is one such formatting option
|
||||
var tx = new Date(t),
|
||||
ts = chartOptions.timestampFormatter(tx),
|
||||
tsWidth = context.measureText(ts).width;
|
||||
|
||||
textUntilX = chartOptions.scrollBackwards
|
||||
? gx + tsWidth + 2
|
||||
: gx - tsWidth - 2;
|
||||
|
||||
context.fillStyle = chartOptions.labels.fillStyle;
|
||||
if(chartOptions.scrollBackwards) {
|
||||
context.fillText(ts, gx, dimensions.height - 2);
|
||||
} else {
|
||||
context.fillText(ts, gx - tsWidth, dimensions.height - 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context.restore(); // See .save() above.
|
||||
};
|
||||
|
||||
// Sample timestamp formatting function
|
||||
SmoothieChart.timeFormatter = function(date) {
|
||||
function pad2(number) { return (number < 10 ? '0' : '') + number }
|
||||
return pad2(date.getHours()) + ':' + pad2(date.getMinutes()) + ':' + pad2(date.getSeconds());
|
||||
};
|
||||
|
||||
exports.TimeSeries = TimeSeries;
|
||||
exports.SmoothieChart = SmoothieChart;
|
||||
|
||||
})(typeof exports === 'undefined' ? this : exports);
|
67
sensortoy/sensortoy/app/partials/cc2650_humidity_dial.ejs
Normal file
67
sensortoy/sensortoy/app/partials/cc2650_humidity_dial.ejs
Normal file
@ -0,0 +1,67 @@
|
||||
<div class="mui-row">
|
||||
<div class="mui-col-xs-3 mui--text-accent mui--text-right">Temp:</div>
|
||||
<div class="mui-col-xs-3 mui--text-light" id="<%= data.baseID + '-t' %>">--</div>
|
||||
<div class="mui-col-xs-3 mui--text-accent mui--text-right">Humidity:</div>
|
||||
<div class="mui-col-xs-3 mui--text-light" id="<%= data.baseID + '-h' %>">--</div>
|
||||
</div>
|
||||
<ul class="mui-tabs__bar mui-tabs__bar--justified">
|
||||
<li class="tabOnWhite"><a data-mui-toggle="tab"
|
||||
data-mui-controls="<%= data.baseID + '-t-pane' %>">Temperature</a>
|
||||
</li>
|
||||
<li class="tabOnWhite mui--is-active"><a data-mui-toggle="tab"
|
||||
data-mui-controls="<%= data.baseID + '-h-pane' %>">Humidity</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="mui-tabs__pane" id="<%= data.baseID + '-t-pane' %>">
|
||||
<svg width='300' height='200' viewbox='0 0 300 220' background="#212121" id="<%= data.baseID + 'temp-svg' %>">
|
||||
|
||||
<g transform="translate(-90,40) rotate(-120 180 90)" >
|
||||
<path id="<%= data.baseID + 'temp-arcB' %>" fill="none" stroke="rgb(61, 61, 61)" stroke-width="12" />
|
||||
<path id="<%= data.baseID + 'temp-arc' %>" fill="none" stroke="#00bfff" stroke-width="12" />
|
||||
</g>
|
||||
<g transform="translate(150,100)">
|
||||
<text id="<%= data.baseID + 'temp-label' %>" class='textLabel' x='0' y='100' fill='#bad649' font-size='50' >--ºc</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
<!--
|
||||
<svg id="<%= data.baseID + 'temp-svg' %>" width="300" height="150" fill="blue">
|
||||
<line x1="46" y1="12" x2="280" y2="12"
|
||||
style="stroke:#004c6d;stroke-width:2;"></line>
|
||||
<text id="<%= data.baseID + 'temp-txt1' %>" x="36" y="15" text-anchor="end">--</text>
|
||||
<line x1="46" y1="136" x2="280" y2="136"
|
||||
style="stroke:#004c6d;stroke-width:2;"></line>
|
||||
<text id="undefined" x="36" y="139" text-anchor="end">0</text>
|
||||
<polyline id="<%= data.baseID + 'temp-line' %>" fill="none" stroke="#2196F3"
|
||||
text-anchor="end" stroke-width="2"
|
||||
points=""></polyline>
|
||||
</svg>
|
||||
-->
|
||||
</div>
|
||||
<div class="mui-tabs__pane mui--is-active" id="<%= data.baseID + '-h-pane' %>">
|
||||
|
||||
<svg width='300' height='200' viewbox='0 0 300 220' background="#212121" id="<%= data.baseID + 'humidity-svg' %>">
|
||||
|
||||
<g transform="translate(-90,40) rotate(-120 180 90)" >
|
||||
<path id="<%= data.baseID + 'humidity-arcB' %>" fill="none" stroke="rgb(61, 61, 61)" stroke-width="12" />
|
||||
<path id="<%= data.baseID + 'humidity-arc' %>" fill="none" stroke="#00bfff" stroke-width="12" />
|
||||
</g>
|
||||
<g transform="translate(150,100)">
|
||||
<text id="<%= data.baseID + 'humidity-label' %>" class='textLabel' x='0' y='100' fill='#bad649' font-size='50' >--ºc</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
||||
<!--<svg id="<%= data.baseID + 'humidity-svg' %>" width="300" height="150" fill="blue">
|
||||
<line x1="46" y1="12" x2="280" y2="12"
|
||||
style="stroke:#004c6d;stroke-width:2;"></line>
|
||||
<text id="<%= data.baseID + 'humidity-txt1' %>" x="36" y="15" text-anchor="end">--</text>
|
||||
<line x1="46" y1="136" x2="280" y2="136"
|
||||
style="stroke:#004c6d;stroke-width:2;"></line>
|
||||
<text id="undefined" x="36" y="139" text-anchor="end">0
|
||||
</text>
|
||||
<polyline id="<%= data.baseID + 'humidity-line' %>" fill="none" stroke="#2196F3"
|
||||
text-anchor="end" stroke-width="2"
|
||||
points=""></polyline>
|
||||
</svg>-->
|
||||
</div>
|
@ -16,6 +16,7 @@ var stripDebug = require('gulp-strip-debug');
|
||||
var scss = require('gulp-scss');
|
||||
var sass = require('gulp-sass');
|
||||
var googleWebFonts = require('gulp-google-webfonts');
|
||||
var sloc = require('gulp-sloc');
|
||||
|
||||
|
||||
var filePath = {
|
||||
@ -24,6 +25,12 @@ var filePath = {
|
||||
|
||||
var fontOptions = { };
|
||||
|
||||
gulp.task('sloc', function(){
|
||||
gulp.src(['app/js/**/*.js'])
|
||||
.pipe(sloc());
|
||||
});
|
||||
|
||||
|
||||
gulp.task('appJS', function() {
|
||||
return gulp.src(['app/js/mandecoder.js','app/js/index.js'])
|
||||
.pipe(stripDebug())
|
||||
@ -63,9 +70,17 @@ gulp.task('devices', function() {
|
||||
.pipe(gulp.dest('www/js'));
|
||||
});
|
||||
|
||||
/*
|
||||
<script type="text/javascript" src="libs/jsfft/complex_array.js"></script>
|
||||
<script type="text/javascript" src="libs/jsfft/fft.js"></script>
|
||||
|
||||
*/
|
||||
gulp.task('vendor', function() {
|
||||
return gulp.src(['app/libs/jquery/dist/jquery.min.js',
|
||||
'app/libs/mui/packages/cdn/js/mui.min.js'])
|
||||
'app/libs/mui/packages/cdn/js/mui.min.js',
|
||||
'app/libs/ejs/ejs_production.js',
|
||||
'app/libs/jsfft/complex_array.js',
|
||||
'app/libs/jsfft/fft.js'])
|
||||
.pipe(concat('vendor.js'))
|
||||
.pipe(uglify({mangle: false}))
|
||||
.pipe(gulp.dest('www/libs'));
|
||||
@ -123,7 +138,7 @@ gulp.task('index', function() {
|
||||
|
||||
gulp.task('partials', function() {
|
||||
|
||||
// Gulp.src(['app/partials/**/*']).pipe(gulp.dest('dist/partials'));
|
||||
gulp.src(['app/partials/**/*']).pipe(gulp.dest('www/partials'));
|
||||
// gulp.src(['app/libs/ejs_production.js']).pipe(gulp.dest('dist/libs'));
|
||||
// gulp.src(['app/libs/microevent.js']).pipe(gulp.dest('dist/libs'));
|
||||
//gulp.src(['app/fav/**/*']).pipe(size({title: 'Partials'})).pipe(gulp.dest('dist/fav'));
|
||||
@ -146,11 +161,11 @@ gulp.task('clean', function() {
|
||||
|
||||
gulp.task('normal', ['clean'], function() {
|
||||
'use strict';
|
||||
gulp.start('copy','watch');
|
||||
gulp.start('copy', 'sloc','watch');
|
||||
});
|
||||
|
||||
gulp.task('default', ['clean'], function() {
|
||||
gulp.start('buildJS','mui','styles','fonts', 'index', 'partials');
|
||||
gulp.start('buildJS','mui','styles','fonts', 'index', 'partials', 'sloc');
|
||||
});
|
||||
|
||||
gulp.task('watch',function() {
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,213 +1,2 @@
|
||||
|
||||
#lightR, #projR { color: red !important; }
|
||||
|
||||
#lightG, #projG { color: green !important; }
|
||||
|
||||
#lightB, #projB { color: blue !important; }
|
||||
|
||||
#lightW, #projW { background-color: #aabbcc; }
|
||||
|
||||
.lightBG, .heatingBG, .projectorBG {
|
||||
float: right;
|
||||
}
|
||||
|
||||
|
||||
.h105 {
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.mdHeading {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
.item_content {
|
||||
height: 100px;
|
||||
/* border: 1px solid grey;*/
|
||||
min-height: 100px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.item_content a.title {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.item_content div.body, .item_content div.site, .item_content div.tags {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
color: #313131;
|
||||
}
|
||||
|
||||
.time, .date, .temp {
|
||||
font-family: 'Ubuntu Condensed', sans-serif;
|
||||
font-size: 80px;
|
||||
color: #bad649;
|
||||
}
|
||||
|
||||
.time span.hour:after {
|
||||
content: ":";
|
||||
}
|
||||
|
||||
.date {
|
||||
font-size: 35px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.temp::after {
|
||||
content: "°c";
|
||||
}
|
||||
|
||||
.item_content div.tags {
|
||||
color: blue;
|
||||
}
|
||||
|
||||
.noConnection {
|
||||
color: rgb(244, 150, 26);
|
||||
}
|
||||
|
||||
#caltext {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* Smartphones (portrait and landscape) ----------- */
|
||||
@media only screen and (min-device-width: 320px) and (max-device-width: 480px) {
|
||||
/* Styles */
|
||||
|
||||
}
|
||||
|
||||
/* Smartphones (landscape) ----------- */
|
||||
@media only screen and (min-width: 321px) {
|
||||
/* Styles */
|
||||
}
|
||||
|
||||
/* Smartphones (portrait) ----------- */
|
||||
@media only screen and (max-width: 320px) {
|
||||
/* Styles */
|
||||
}
|
||||
|
||||
.spinner {
|
||||
margin: 25px auto 0;
|
||||
width: 70px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.spinner > div {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
background-color: rgb(244, 150, 26);
|
||||
border-radius: 100%;
|
||||
display: inline-block;
|
||||
-webkit-animation: sk-bouncedelay 1.4s infinite ease-in-out both;
|
||||
animation: sk-bouncedelay 1.4s infinite ease-in-out both;
|
||||
}
|
||||
|
||||
.spinner .bounce1 {
|
||||
-webkit-animation-delay: -0.32s;
|
||||
animation-delay: -0.32s;
|
||||
}
|
||||
|
||||
.spinner .bounce2 {
|
||||
-webkit-animation-delay: -0.16s;
|
||||
animation-delay: -0.16s;
|
||||
}
|
||||
|
||||
@-webkit-keyframes sk-bouncedelay {
|
||||
0%, 80%, 100% { -webkit-transform: scale(0) }
|
||||
40% { -webkit-transform: scale(1.0) }
|
||||
}
|
||||
|
||||
@keyframes sk-bouncedelay {
|
||||
0%, 80%, 100% {
|
||||
-webkit-transform: scale(0);
|
||||
transform: scale(0);
|
||||
}
|
||||
40% {
|
||||
-webkit-transform: scale(1.0);
|
||||
transform: scale(1.0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.md-display {
|
||||
opacity: 1;
|
||||
transition: opacity 0.3s, visibility 0.3s;
|
||||
|
||||
}
|
||||
|
||||
.lostConnection {
|
||||
opacity: 0.5;
|
||||
transition: opacity 0.3s, visibility 0.3s;
|
||||
}
|
||||
|
||||
|
||||
.mui-ellipsis-2 {
|
||||
display: -webkit-box;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
word-wrap: break-word;
|
||||
white-space: normal!important;
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.pulser {
|
||||
display: block;
|
||||
margin-top:10%;
|
||||
border-radius: 100px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border: 10px solid #C5F4EB;
|
||||
-webkit-animation: pulse 0.75s ease-in infinite;
|
||||
-moz-animation: pulse 0.75s ease-in infinite;
|
||||
animation: pulse 0.75s ease-in infinite;
|
||||
}
|
||||
@-webkit-keyframes pulse {
|
||||
0% { -webkit-transform: scale(0); }
|
||||
85% { opacity: 1; }
|
||||
100% { -webkit-transform: scale(1); -webkit-filter: blur(5px); opacity: 0; }
|
||||
}
|
||||
@-moz-keyframes pulse {
|
||||
0% { -moz-transform: scale(0); }
|
||||
85% { opacity: 1; }
|
||||
100% { -moz-transform: scale(1); -moz-filter: blur(5px); opacity: 0; }
|
||||
}
|
||||
@keyframes pulse {
|
||||
0% { transform: scale(0); }
|
||||
85% { opacity: 1; }
|
||||
100% { transform: scale(1); filter: blur(5px); opacity: 0; }
|
||||
}
|
||||
|
||||
|
||||
header {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
right:0;
|
||||
transition: left .2s;
|
||||
width:100%;
|
||||
}
|
||||
|
||||
.mui-tabs__bar > li.tabOnWhite > a
|
||||
|
||||
{
|
||||
color:#78909C;
|
||||
}
|
||||
|
||||
.mui-tabs__bar > li.mui--is-active.tabOnWhite > a
|
||||
{
|
||||
color:#2196F3;
|
||||
}
|
||||
|
||||
|
||||
/* svg classes */
|
||||
|
||||
svg text {
|
||||
font-family:'Ubuntu Condensed',sans-serif;
|
||||
font-size:12;
|
||||
fill: #bad649;
|
||||
text-align:right;
|
||||
}
|
||||
#lightR,#projR{color:red!important}#lightG,#projG{color:green!important}#lightB,#projB{color:blue!important}#lightW,#projW{background-color:#abc}.heatingBG,.lightBG,.projectorBG{float:right}.h105{height:100px}.item_content,.mdHeading{overflow:hidden}.item_content{height:100px;min-height:100px}.item_content a.title{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#fff}.item_content div.body,.item_content div.site,.item_content div.tags{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#313131}.date,.temp,.time{font-family:Ubuntu Condensed,sans-serif;font-size:80px;color:#bad649}.time span.hour:after{content:":"}.date{font-size:35px;line-height:1}.temp:after{content:"°c"}.item_content div.tags{color:blue}.noConnection{color:#f4961a}#caltext{color:#fff}.spinner{margin:25px auto 0;width:70px;text-align:center}.spinner>div{width:18px;height:18px;background-color:#f4961a;border-radius:100%;display:inline-block;animation:a 1.4s infinite ease-in-out both}.spinner .bounce1{animation-delay:-.32s}.spinner .bounce2{animation-delay:-.16s}@keyframes a{0%,80%,to{transform:scale(0)}40%{transform:scale(1)}}.md-display{opacity:1}.lostConnection,.md-display{transition:opacity .3s,visibility .3s}.lostConnection{opacity:.5}.mui-ellipsis-2{display:-webkit-box;overflow:hidden;text-overflow:ellipsis;word-wrap:break-word;white-space:normal!important;-webkit-line-clamp:1;-webkit-box-orient:vertical}.pulser{display:block;margin-top:10%;border-radius:100px;width:30px;height:30px;border:10px solid #c5f4eb;animation:b .75s ease-in infinite}@keyframes b{0%{transform:scale(0)}85%{opacity:1}to{transform:scale(1);-webkit-filter:blur(5px);filter:blur(5px);opacity:0}}header{position:fixed;top:0;z-index:1;right:0;transition:left .2s;width:100%}.mui-tabs__bar>li.tabOnWhite>a{color:#78909c}.mui-tabs__bar>li.mui--is-active.tabOnWhite>a{color:#2196f3}svg text{font-family:Ubuntu Condensed,sans-serif;font-size:12;fill:#bad649;text-align:right}
|
||||
.progress{position:relative;height:4px;display:block;width:100%;background-color:#acece6;border-radius:2px;background-clip:padding-box;overflow:hidden}.progress .determinate{position:absolute;background-color:inherit;top:0;bottom:0;transition:width .3s linear}.progress .determinate,.progress .indeterminate{background-color:#26a69a}.progress .indeterminate:before{animation:a 2.1s cubic-bezier(.65,.815,.735,.395) infinite}.progress .indeterminate:after,.progress .indeterminate:before{content:'';position:absolute;background-color:inherit;top:0;left:0;bottom:0;will-change:left,right}.progress .indeterminate:after{animation:b 2.1s cubic-bezier(.165,.84,.44,1) infinite;animation-delay:1.15s}@keyframes a{0%{left:-35%;right:100%}60%{left:100%;right:-90%}to{left:100%;right:-90%}}@keyframes b{0%{left:-200%;right:100%}60%{left:107%;right:-8%}to{left:107%;right:-8%}}
|
File diff suppressed because one or more lines are too long
@ -1,10 +1,3 @@
|
||||
@font-face {
|
||||
font-family: 'Material Icons';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url(Material_Icons-normal-400.woff) format('woff');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Ubuntu';
|
||||
font-style: normal;
|
||||
|
@ -1,130 +1 @@
|
||||
<!DOCTYPE html><!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
-->
|
||||
<html>
|
||||
<head>
|
||||
<!--
|
||||
Customize this policy to fit your own app's needs. For more guidance, see:
|
||||
https://github.com/apache/cordova-plugin-whitelist/blob/master/README.md#content-security-policy
|
||||
Some notes:
|
||||
* gap: is required only on iOS (when using UIWebView) and is needed for JS->native communication
|
||||
* https://ssl.gstatic.com is required only on Android and is needed for TalkBack to function properly
|
||||
* Disables use of inline scripts in order to mitigate risk of XSS vulnerabilities. To change this:
|
||||
* Enable inline JS: add 'unsafe-inline' to default-src
|
||||
-->
|
||||
<meta http-equiv="Content-Security-Policy"
|
||||
content="default-src 'self' data: gap: https://ssl.gstatic.com 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src *">
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
<meta name="msapplication-tap-highlight" content="no">
|
||||
<meta name="viewport"
|
||||
content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width">
|
||||
<!-- build:fonts -->
|
||||
<link rel="stylesheet" href="fonts/fonts.css">
|
||||
<!-- endbuild -->
|
||||
|
||||
<!-- build:mui -->
|
||||
<link href="css/mui.custom.css" rel="stylesheet" type="text/css"/>
|
||||
<!-- endbuild -->
|
||||
<!-- build:css -->
|
||||
<link href="css/app.css" rel="stylesheet" type="text/css"/>
|
||||
<link href="css/ripple.css" rel="stylesheet" type="text/css"/>
|
||||
<link href="css/progress.css" rel="stylesheet" type="text/css"/>
|
||||
<!-- endbuild -->
|
||||
<title>Sensor Toy</title>
|
||||
</head>
|
||||
<body class="mui--no-user-select">
|
||||
<header id="header">
|
||||
<div class="mui-appbar">
|
||||
<ul id='app-tabs' class="mui-tabs__bar mui--z1 mui--appbar-height">
|
||||
<li class="mui--is-active"><a data-mui-toggle="tab"
|
||||
data-mui-controls="pane-default-1">Devices</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</header>
|
||||
<div class="mui--appbar-height"></div>
|
||||
<div id="tab-panes" class='' style="height:100%;">
|
||||
<div class="mui-tabs__pane mui--is-active" id="pane-default-1">
|
||||
<div id='app' class="mui-container">
|
||||
<div>
|
||||
<div class="mui-row mui--appbar-height mui--appbar-line-height">
|
||||
<div class="mui-col-xs-6 mui--text-subhead">
|
||||
<span class="">Devices</span>
|
||||
</div>
|
||||
<div class="mui-col-xs-6 mui--text-right">
|
||||
<button class="mui-btn mui-btn--small mui-btn--primary"
|
||||
id="scan">Scan
|
||||
</button>
|
||||
<button class="mui-btn mui-btn--small mui-btn--danger" id="stop"
|
||||
style="display: none;">Stop
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin: 0.5rem 0 1rem 0;">
|
||||
<div class="progress" id='ripple' style="display: none;">
|
||||
<div class="indeterminate"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="scanResults"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript" src="cordova.js"></script>
|
||||
|
||||
<!-- build:vendor -->
|
||||
<script type="text/javascript" src="libs/jquery/dist/jquery.js"></script>
|
||||
<script type="text/javascript" src="libs/mui/packages/cdn/js/mui.js"></script>
|
||||
<script type="text/javascript" src="libs/ejs/ejs.js"></script>
|
||||
<script type="text/javascript"
|
||||
src="libs/bluebird/js/browser/bluebird.js"></script>
|
||||
<!-- endbuild -->
|
||||
|
||||
<!-- build:standards -->
|
||||
<script type="text/javascript"
|
||||
src="js/standards/bluetooth_company_identifiers.js"></script>
|
||||
<script type="text/javascript" src="js/standards/capability.js"></script>
|
||||
<script type="text/javascript" src="js/standards/battery.js"></script>
|
||||
<script type="text/javascript" src="js/standards/button.js"></script>
|
||||
<script type="text/javascript" src="js/mandecoder.js"></script>
|
||||
<!-- endbuild -->
|
||||
|
||||
<!-- build:cc2650 -->
|
||||
<script type="text/javascript"
|
||||
src="js/device/CC2650/cc2650_accelerometer.js"></script>
|
||||
<script type="text/javascript"
|
||||
src="js/device/CC2650/cc2650_barometer.js"></script>
|
||||
<script type="text/javascript"
|
||||
src="js/device/CC2650/cc2650_luxometer.js"></script>
|
||||
<script type="text/javascript"
|
||||
src="js/device/CC2650/cc2650_thermopile.js"></script>
|
||||
<script type="text/javascript"
|
||||
src="js/device/CC2650/cc2650_humidity.js"></script>
|
||||
<!-- endbuild -->
|
||||
|
||||
<script type="text/javascript"
|
||||
src="js/device/fmc/TSL.js"></script>
|
||||
|
||||
|
||||
<!-- build:js -->
|
||||
<script type="text/javascript" src="js/index.js"></script>
|
||||
<!-- endbuild -->
|
||||
</body>
|
||||
</html>
|
||||
<!DOCTYPE html><html><head><meta http-equiv="Content-Security-Policy" content="default-src 'self' data: gap: https://ssl.gstatic.com 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src *"><meta name="format-detection" content="telephone=no"><meta name="msapplication-tap-highlight" content="no"><meta name="viewport" content="user-scalable=no,initial-scale=1,maximum-scale=1,minimum-scale=1,width=device-width"><link rel="stylesheet" href="fonts/fonts.css"><link rel="stylesheet" href="css/custom.css"><link rel="stylesheet" href="css/app.css"><title>Sensor Toy</title></head><body class="mui--no-user-select"><header id="header"><div class="mui-appbar"><ul id="app-tabs" class="mui-tabs__bar mui--z1 mui--appbar-height"><li class="mui--is-active"><a data-mui-toggle="tab" data-mui-controls="pane-default-1">Devices</a></li></ul></div></header><div class="mui--appbar-height"></div><div id="tab-panes" class="" style="height:100%"><div class="mui-tabs__pane mui--is-active" id="pane-default-1"><div id="app" class="mui-container"><div><div class="mui-row mui--appbar-height mui--appbar-line-height"><div class="mui-col-xs-6 mui--text-subhead"><span class="">Devices</span></div><div class="mui-col-xs-6 mui--text-right"><button class="mui-btn mui-btn--small mui-btn--primary" id="scan">Scan</button> <button class="mui-btn mui-btn--small mui-btn--danger" id="stop" style="display: none">Stop</button></div></div><div style="margin: 0.5rem 0 1rem 0"><div class="progress" id="ripple" style="display: none"><div class="indeterminate"></div></div></div><div id="scanResults"></div></div></div></div></div><script type="text/javascript" src="cordova.js"></script><script src="libs/vendor.js"></script><script src="js/standards.js"></script><script src="js/cc2650.js"></script><script type="text/javascript" src="js/device/fmc/TSL.js"></script><script src="js/app.js"></script></body></html>
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user