Hey everyone
I am currently trying to design a display for ECG and pulse sensors with my BeagleBone Black.
I am totally new to this kind of programming so I began buy reading “Programming the BeagleBone Black - Getting Started with JavaScript and BoneScript” by Simon Monk. I have done all the exercises and then worked on my project.
Here is how it works :
1/ I run a Javascript server on my BBB which read (using b.analogRead) the values from the ECG sensor (plugged in P9_36) and from two others pulse sensors (in P9_33 and P9_35) with a function I call every 8 ms (with setInterval)
2/ I run an html file to access my server to display the values
Here are my problems :
In the 1/ step I have set my program to read the values of my sensors at 8ms so that my sample rate is about 120Hz (which is the minimum I can do to process ECG) but when I launch my program for 10 sec I just acquire like 860 to 950 values which correspond more to 10 ms even less.
The first problem is that I always have a different amount of values.
The second problem is that this amount is to low compare to the sampling rate I expect.
My clues for solutions :
I was first thinking that it might due to the BBB which cannot sample at that rate from the ADC, and also the clock inside is a bit rough which explain the inconsistency in the amount of values.
Then I figured out that it might be my program which is not efficient enough.
If anyone has a clue to help me with this problem it would be very nice.
I add my program files (my html/css is not very nice because i did not worked on it yet).
Best regards,
Matthieu Hoel.
The Javascript server :
`
//////////////////////////////////////////////////////////////////////////////////////////////////
// IMPORT : //
// Importing the modules the code requires //
//////////////////////////////////////////////////////////////////////////////////////////////////
//http and file system libraries needed for web server
var app = require(‘http’).createServer(handler);
var fs = require(‘fs’);
//socket.io needed for socket communication with the browser
var io = require(‘socket.io’).listen(app);
//BeagleBone language
var b = require(‘bonescript’);
//Web page that is to be served when a web request comes in
var htmlPage = ‘metrycommented.html’;
//Starts web server listening for request on port 8085
app.listen(8085);
var soc;
var tmr;
//////////////////////////////////////////////////////////////////////////////////////////////////
// HANDLER : //
// Handling any incoming web requests //
// When it receives one, it reads the contents of the file .html or .js or .css or .jpg and //
// sends it to the browser //
//////////////////////////////////////////////////////////////////////////////////////////////////
function handler(req, res)
{
console.log("Received Request: " + req.url);
if(req.url.indexOf(’.html’) != -1)
{
//call to readFile specifies a callback for read being completed
fs.readFile(’.’ + req.url, function (error, data)
{
if (error)
{
//response is used to tell the browser that the page is not ok (status code 404) + JSON content of the pqge
res.writeHead(404, {“Content-type”:“text/plain”});
res.end(“No Html Page Found.”);
}
else
{
//response is used to tell the browser that the page is ok (status code 200)
res.writeHead(200, {‘Content-Type’: ‘text/html’});
//end is used to end the request and at the same time send the whole contents of the file
res.end(data);
}
});
}
else if(req.url.indexOf(’.js’) != -1)
{
console.log("js Request: " + ‘.’ + req.url);
fs.readFile(’.’ + req.url, function (error, data)
{
if (error)
{
res.writeHead(404, {“Content-type”:“text/plain”});
res.end(“No Javascript Page Found.”);
}
else
{
res.writeHead(200, {‘Content-Type’: ‘text/javascript’});
console.log("js send ");
res.end(data);
}
});
}
else if(req.url.indexOf(’.css’) != -1)
{
fs.readFile(’.’ + req.url, function (error, data)
{
if (error)
{
res.writeHead(404, {“Content-type”:“text/plain”});
res.end(“No Css Page Found.”);
}
else
{
res.writeHead(200, {‘Content-Type’: ‘text/css’});
res.end(data);
}
});
}
else if(req.url.indexOf(’.jpg’) != -1)
{
console.log("jpg Request: " + req.url);
fs.readFile(’.’ + req.url, function (error, data)
{
if (error)
{
res.writeHead(404, {“Content-Type”: “image/jpeg”});
res.end(“No Image Page Found.”);
}
else
{
res.writeHead(200, {‘Content-Type’: ‘image/jpeg’});
res.end(data);
}
});
}
else
{
fs.readFile(htmlPage, function(err, data)
{
if (err)
{
res.writeHead(500);
res.end('Error loading file: ’ + htmlPage);
}
else
{
res.writeHead(200);
res.end(data);
}
});
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////
// Filters //
// Sampling rate is 100 Hz //
//////////////////////////////////////////////////////////////////////////////////////////////////
function IIR10Filter (inp) //II order filter using the values from Koh Bee Hock David
{
var inp2 = 0;
//Gains
var dGain1 = 0.569743229;
var dGain2 = 0.45713223;
var dGain3 = 0.391335773;
var dGain4 = 0.35428676;
var dGain5 = 0.337489037;
//Coefficients for the lowpassfilter
var dCoefficient11 = 0.537992527;
var dCoefficient12 = 0.740980389;
var dCoefficient21 = 0.431657124;
var dCoefficient22 = 0.396871795;
var dCoefficient31 = 0.369527377;
var dCoefficient32 = 0.195815713;
var dCoefficient41 = 0.334543036;
var dCoefficient42 = 0.082604006;
var dCoefficient51 = 0.318681417;
var dCoefficient52 = 0.03127473;
var xv1 = [0,0,0];
var yv1 = [0,0,0];
var xv2 = [0,0,0];
var yv2 = [0,0,0];
var xv3 = [0,0,0];
var yv3 = [0,0,0];
var xv4 = [0,0,0];
var yv4 = [0,0,0];
var xv5 = [0,0,0];
var yv5 = [0,0,0];
//Process 1
xv1[0] = xv1[1];
xv1[1] = xv1[2];
xv1[2] = (inp / dGain1);
yv1[0] = yv1[1];
yv1[1] = yv1[2];
yv1[2] = ((xv1[0] + xv1[2]) + 2 * xv1[1] + (dCoefficient11 * yv1[0]) + ( dCoefficient12 * yv1[1]));
inp = yv1[2];
//Process 2
xv2[0] = xv2[1];
xv2[1] = xv2[2];
xv2[2] = (inp / dGain2);
yv2[0] = yv2[1];
yv2[1] = yv2[2];
yv2[2] = ((xv2[0] + xv2[2]) + 2 * xv2[1] + (dCoefficient21 * yv2[0]) + ( dCoefficient22 * yv2[1]));
inp = yv2[2];
//Process 3
xv3[0] = xv3[1];
xv3[1] = xv3[2];
xv3[2] = (inp / dGain3);
yv3[0] = yv3[1];
yv3[1] = yv3[2];
yv3[2] = ((xv3[0] + xv3[2]) + 2 * xv3[1] + (dCoefficient31 * yv3[0]) + ( dCoefficient32 * yv3[1]));
inp = yv3[2];
//Process 4
xv4[0] = xv4[1];
xv4[1] = xv4[2];
xv4[2] = (inp / dGain4);
yv4[0] = yv4[1];
yv4[1] = yv4[2];
yv4[2] = ((xv4[0] + xv4[2]) + 2 * xv4[1] + (dCoefficient41 * yv4[0]) + ( dCoefficient42 * yv4[1]));
inp = yv4[2];
//Process 5
xv5[0] = xv5[1];
xv5[1] = xv5[2];
xv5[2] = (inp / dGain5);
yv5[0] = yv5[1];
yv5[1] = yv5[2];
yv5[2] = ((xv5[0] + xv5[2]) + 2 * xv5[1] + (dCoefficient51 * yv5[0]) + ( dCoefficient52 * yv5[1]));
inp = yv5[2];
inp2 = inp;
inp =0;
return inp2;
}
function LP4Filter (inp)
{ //Butterworth Filter :
//This function uses the Butterworth filter and returns a new value for an individual floating point value.
//Second Order System : Infinite Impulse Response
//Lowpass filter cutoff frequency 27Hz: for noisy environment, to detect heart rate
//Gain for the lowpass filter
var dGain = 8.352218539;
//Coefficients for the bandpassfilter
var dCoefficient1 = -0.0195666466;
var dCoefficient2 = -0.0667120257;
var dCoefficient3 = -0.5169062239;
var dCoefficient4 = -0.3124737907;
var xv = [0,0,0,0,0];
var yv = [0,0,0,0,0];
//Process
xv[0] = xv[1];
xv[1] = xv[2];
xv[2] = xv[3];
xv[3] = xv[4];
xv[4] = (inp / dGain);
yv[0] = yv[1];
yv[1] = yv[2];
yv[2] = yv[3];
yv[3] = yv[4];
yv[4] = (xv[0] + xv[4]) + 4 * (xv[1] + xv[3]) + 6 * xv[2] + ( dCoefficient1 * yv[0]) + ( dCoefficient2 * yv[1]) + ( dCoefficient3 * yv[2]) + ( dCoefficient4 * yv[3]);
inp = yv[4];
return inp;
}
function LPFilter (inp) //Lowpass filter cutoff frequency 27Hz
{
//Gain for the lowpass filter
var dGain = 3.024048313;
//Coefficients for the lowpassfilter
var dCoefficient1 =-0.1754120032;
var dCoefficient2 = -0.1473181871;
var xv = [0,0,0];
var yv = [0,0,0];
//Process
xv[0] = xv[1];
xv[1] = xv[2];
xv[2] = (inp / dGain);
yv[0] = yv[1];
yv[1] = yv[2];
yv[2] = ((xv[0] + xv[2]) + 2 * xv[1] + (dCoefficient1 * yv[0]) + ( dCoefficient2 * yv[1]));
inp = yv[2];
return inp;
}
function HP4Filter (inp)
{ //Butterworth Filter :
//This function uses the Butterworth filter and returns a new value for an individual floating point value.
//Second Order System : Infinite Impulse Response
//Highpass filter cutoff frequency 0.5Hz: filtering for noisy environment, to detect heart rate
//Gain for the bandpass filter
var dGain = 1.041903016;
//Coefficients for the bandpassfilter
var dCoefficient1 = -0.9211819292;
var dCoefficient2 =3.7603495077;
var dCoefficient3 = -5.7570763791;
var dCoefficient4 = 3.9179078654;
var xv = [0,0,0,0,0];
var yv = [0,0,0,0,0];
//Process
xv[0] = xv[1];
xv[1] = xv[2];
xv[2] = xv[3];
xv[3] = xv[4];
xv[4] = (inp / dGain);
yv[0] = yv[1];
yv[1] = yv[2];
yv[2] = yv[3];
yv[3] = yv[4];
yv[4] = (xv[0] + xv[4]) - 4 * (xv[1] + xv[3]) + 6 * xv[2] + ( dCoefficient1 * yv[0]) + ( dCoefficient2 * yv[1]) + ( dCoefficient3 * yv[2]) + ( dCoefficient4 * yv[3]);
inp = yv[4];
return inp;
}
function HPFilter (inp) //Highpass filter cutoff frequency 0.5Hz
{
//Gain for the highpass filter
var dGain = 1.022463023;
//Coefficients for the highpassfilter
var dCoefficient1 = -0.9565436765;
var dCoefficient2 = 1.9555782403;
var xvh1 = [0,0,0];
var yvh1 = [0,0,0];
var xvh2 = [0,0,0];
var yvh2 = [0,0,0];
//Process
xvh1[0] = xvh1[1];
xvh1[1] = xvh1[2];
xvh1[2] = (inp / dGain);
yvh1[0] = yvh1[1];
yvh1[1] = yvh1[2];
yvh1[2] = ((xvh1[0] + xvh1[2]) - 2 * xvh1[1] + (dCoefficient1 * yvh1[0]) + ( dCoefficient2 * yvh1[1]));
inp = yvh1[2];
//Process
xvh2[0] = xvh2[1];
xvh2[1] = xvh2[2];
xvh2[2] = (inp / dGain);
yvh2[0] = yvh2[1];
yvh2[1] = yvh2[2];
yvh2[2] = ((xvh2[0] + xvh2[2]) - 2 * xvh2[1] + (dCoefficient1 * yvh2[0]) + ( dCoefficient2 * yvh2[1]));
inp = yvh2[2];
return inp;
}
//////////////////////////////////////////////////////////////////////////////////////////////////
// PEAK DETECTION / Pulse transit time: //
// Mean chanel 1, 2, 3 //
// Standard deviation chanel 1, 2, 3 //
//////////////////////////////////////////////////////////////////////////////////////////////////
//Initialize lookforpeak so that it enter in the first if loop during peak detection
var lookforpeak1 = 1; var lookforpeak2 = 1; var lookforpeak3 = 1;
var pos=0;
//Initialize the max value and position (peak coordinates)
var mxval1 = 0; var mxval2 = 0; var mxval3 = 0;
var mxpos1 = 0; var mxpos2 = 0; var mxpos3 = 0;
//Initialize the threshold value
var threshold1 = 0.03; var threshold2 = 0.03; var threshold3 = 0.03;
//Array in which is stored the peak values and position
var pkval1 = new Array (0); var pkval2 = new Array (0); var pkval3 = new Array (0);
var pkpos1 = new Array (0); var pkpos2 = new Array (0); var pkpos3 = new Array (0);
//Initialize the min value and position (valley coordinates)
var mnval1 = 1; var mnval2 = 1; var mnval3 = 1;
var mnpos1 = 0; var mnpos2 = 0; var mnpos3 = 0;
//Array in which is stored the valley values and position
var vyval1 = new Array (0); var vyval2 = new Array (0); var vyval3 = new Array (0);
var vypos1 = new Array (0); var vypos2 = new Array (0); var vypos3 = new Array (0);
var t1 = new Array (0); var t2 = new Array (0); var t3 = new Array (0);
//Missed peaks on each chanel
var Miss1=0; var Miss2=0; var Miss3=0;
//Mean of each chanel
var Mean1=0; var Mean2=0; var Mean3=0;
//Standard deviation of each chanel
var Std1=0; var Std2=0; var Std3=0;
//Number of peaks on each chanel
var N1=0, N2=0, N3=0;
var win1=0;
//////////////////////////////////////////////////////////////////////////////////////////////////
// Timers //
// //
//////////////////////////////////////////////////////////////////////////////////////////////////
//Take the time duration parametre
function handleStartTimer(duration)
{
N1=0;
N2=0;
N3=0;
pos=0;
if (duration == -1)
{
handleCancelTimer();
}
else
{
//Executes “checkInputs” fonction at 8 ms intervals => 120Hz sampling
tmr = setInterval(checkInputs, 8);
//If duration is not a number timer is set to default value 30 seconds
if (isNaN(duration))
{
setTimeout(handleCancelTimer, 30000);
}
//Convert duration entered by user in seconds
else
{
tmrTL = setTimeout(handleCancelTimer, duration*1000);
}
}
}
var dbg = 0;
function handleCancelTimer()
{
console.log("p: " + pos + " dbg: " + dbg);
clearInterval(tmr);
if (t1.length>0)
{
Mean1 = 0;
Std1 = 0;
for (var i=0; i < t1.length; i++)
{
// calculate mean of signal 1
Mean1 += t1[i];
Mean1 = Mean1 /t1.length;
// calculate standard deviation of signal 1
Std1 += (t1[i]-Mean1)*(t1[i]-Mean1);
Std1 = Math.sqrt(Std1/(t1.length-1));
}
}
else
{
Mean1 = -1;
Std1 = -1;
}
if (t2.length>0)
{
Mean2 = 0;
Std2 = 0;
for (var i=0; i < t2.length; i++)
{
// calculate mean of signal 2
Mean2 += (t2[i]) / (t2.length);
Mean2 = Mean2 /t2.length;
// calculate standard deviation of signal 2
Std2 += (t2[i]-Mean2)*(t2[i]-Mean2);
Std2 = Math.sqrt(Std2/(t2.length-1));
}
}
else
{
Mean2 = -1;
Std2 = -1;
}
if (t3.length>0)
{
Mean3 = 0;
Std3 = 0;
for (var i=0; i < t3.length; i++)
{
// calculate mean of signal 3
Mean3 += t3[i];
Mean3 = Mean3 /t3.length;
// calculate standard deviation of signal 3
Std3 += (t3[i]-Mean3)*(t3[i]-Mean3);
Std3 = Math.sqrt(Std3/(t3.length-1));
}
}
else
{
Mean3 = -1;
Std3 = -1;
}
//Board emit the results in JSON format
soc.emit(“resultUpdate”, ‘{“result”:“Mean2”, “value”:’ + Mean2 + ‘}’);
soc.emit(“resultUpdate”, ‘{“result”:“Mean3”, “value”:’ + Mean3 + ‘}’);
soc.emit(“resultUpdate”, ‘{“result”:“Std2”, “value”:’ + Std2 + ‘}’);
soc.emit(“resultUpdate”, ‘{“result”:“Std3”, “value”:’ + Std3 + ‘}’);
soc.emit(“resultUpdate”, ‘{“result”:“Miss2”, “value”:’ + Miss2 + ‘}’);
soc.emit(“resultUpdate”, ‘{“result”:“Miss3”, “value”:’ + Miss3 + ‘}’);
}
//////////////////////////////////////////////////////////////////////////////////////////////////
// ON CONNECT : //
// Automatically called when START is clicked on the web interface //
// Call the Start timer handler //
//////////////////////////////////////////////////////////////////////////////////////////////////
function onConnect(socket)
{
//Specify the commands to handle and associate them with a function (here the timer)
socket.on(‘startTimer’, handleStartTimer);
soc = socket;
}
function checkInputs()
{
// auto ends at 5min
pos++;
//reading sensor 1: ECG
b.analogRead(“P9_36”,printVoltage1);
//reading sensor 2: BioS
b.analogRead(“P9_33”,printVoltage2);
//reading sensor 3: PO
b.analogRead(“P9_35”,printVoltage3);
}
//Sensor 1: ECG
function printVoltage1(reading)
{
if (!reading.err)
{
//var newValue = IIR10Filter(reading.value*1.8)0.05; // best for now with drift
//var newValue = HPFilter(IIR10Filter(reading.value)); // Try to reduce baseline drift
//var newValue = HPFilter(LP4Filter(reading.value1.8)*300)400; //cleaner
//var newValue = HPFilter(LPFilter(reading.value1.8)400); // a bit dirty
//var newValue = LP4Filter(reading.value1.8);
var newValue = reading.value; //Display without filters
//Board emit the filtered value converted as a string with 3 decimals
soc.emit(“pinUpdate”, ‘{“pin”:“P9_36”, “value”:’ + newValue.toFixed(3) + ‘}’);
dbg++; //Is incremented each time a new value is read from the board, helps to check the sample rate
win1++;
//Goes into this loop the first time as lookforpeak is initialize at 1
if (lookforpeak1==1)
{
// Adaptive Threshold
if (pkval1.length>0)
{
threshold1 = 0.7 * (pkval1[pkval1.length-1]-vyval1[vyval1.length-1]);
if (threshold1 < 0.01)
{
threshold1 = 0.01;
}
if (threshold1 > 0.15)
{
threshold1 = 0.15;
}
}
// Track, max value update
if (newValue > mxval1)
{
mxval1 = newValue;
mxpos1 = pos;
}
// peak detection
if (newValue < (mxval1-threshold1))
{
pkval1.push(mxval1);
pkpos1.push(mxpos1);
N1++;
//Board emit the peak (+1 each time a new pick is found)
soc.emit(“resultUpdate”, ‘{“result”:“N1”, “value”:’ + N1 + ‘}’);
mnval1 = mxval1;
lookforpeak1 = 0;
win1 = 0;
}
}
else
{
// track
if (newValue < mnval1)
{
mnval1 = newValue;
mnpos1 = pos;
}
// valley detection
if (newValue > (mnval1+threshold1))
{
vyval1.push(mnval1);
vypos1.push(mnpos1);
mxval1 = mnval1;
lookforpeak1 = 1;
}
}
}
}
//Sensor 2: Bios Magnetic (SQUID)
function printVoltage2(reading)
{
if (!reading.err)
{
var newValue = reading.value;
soc.emit(“pinUpdate”, ‘{“pin”:“P9_33”, “value”:’ + newValue.toFixed(3) + ‘}’);
if (lookforpeak2==1)
{
// adaptive threshold
if (pkval2.length>0)
{
threshold2 = 0.7 * (pkval2[pkval2.length-1]-vyval2[vyval2.length-1]);
if (threshold2 < 0.01) threshold2 = 0.01;
if (threshold2 > 0.15) threshold2 = 0.15;
}
// track
if (newValue > mxval2)
{
mxval2 = newValue;
mxpos2 = pos;
}
// peak detection
if (newValue < (mxval2-threshold1))
{
pkval2.push(mxval2);
pkpos2.push(mxpos2);
mnval2 = mxval2;
N2++;
var delay1 = 0;
delay1 = mxpos1-mxpos2;
soc.emit(“resultUpdate”, ‘{“result”:“delay1”, “value”:’ + delay1 + ‘}’);
soc.emit(“resultUpdate”, ‘{“result”:“N2”, “value”:’ + N2 + ‘}’);
lookforpeak2 = 0;
if (win1>5)
{
Miss2++; //missed
}
else
{
t2.push(win1);
}
}
}
else
{
// track
if (newValue < mnval2)
{
mnval2 = newValue;
mnpos2 = pos;
}
// valley detection
if (newValue > (mnval2+threshold2))
{
vyval2.push(mnval2);
vypos2.push(mnpos2);
mxval2 = mnval2;
lookforpeak2 = 1;
}
}
}
}
//Sensor 3: Pulse Oximetry
function printVoltage3(reading)
{
if (!reading.err)
{
var newValue = reading.value*1.800;
soc.emit(“pinUpdate”, ‘{“pin”:“P9_35”, “value”:’ + newValue.toFixed(3) + ‘}’);
if (lookforpeak3==1)
{
// adaptive threshold
if (pkval3.length>0)
{
threshold3 = 0.7 * (pkval3[pkval3.length-1]-vyval3[vyval3.length-1]);
if (threshold3 < 0.01) threshold3 = 0.01;
if (threshold3 > 0.15) threshold3 = 0.15;
}
// track
if (newValue > mxval3)
{
mxval3 = newValue;
mxpos3 = pos;
}
// peak detection
if (newValue < (mxval3-threshold3))
{
pkval3.push(mxval3);
pkpos3.push(mxpos3);
mnval3 = mxval3;
N3++;
var delay2 = 0;
delay2 = mxpos2-mxpos3;
var delay3 = 0;
delay3 = mxpos1-mxpos3;
soc.emit(“resultUpdate”, ‘{“result”:“delay2”, “value”:’ + delay2 + ‘}’);
soc.emit(“resultUpdate”, ‘{“result”:“delay3”, “value”:’ + delay3 + ‘}’);
soc.emit(“resultUpdate”, ‘{“result”:“N3”, “value”:’ + N3 + ‘}’);
lookforpeak3 = 0;
if (win1>5)
{
Miss3++; //missed
}
else {
t3.push(win1);
}
}
}
else
{
// track
if (newValue < mnval3)
{
mnval3 = newValue;
mnpos3 = pos;
}
// valley detection
if (newValue > (mnval3+threshold3))
{
vyval3.push(mnval3);
vypos3.push(mnpos3);
mxval3 = mnval3;
lookforpeak3 = 1;
}
}
}
}
//Ensures that onConnect gets called when communication is established
io.sockets.on(‘connection’, onConnect);
`
The HTML file to access it:
`
BeagleBone BlackBeagleBone Black :
Signal acquisition
Datas
Time in seconds:
- Delay ECG / Bios = -
- Delay ECG / PO = -
- Delay Bios / PO = -
- ECG Peaks = -
- BioS Peaks = -
- PO Peaks = -
- Mean Bios = -
- Mean PO = -
- STD Bios = -
- STD PO = -
- Missed Channel 2 = -
- Missed Channel 3 = -
- P9_36 Value = -
- P9_33 Value = -
- P9_35 Value = -
Display
`
The CSS page :
`
- { padding: 0; margin: 0; vertical-align: top; }
body {
background: url(background.png) repeat-x;
font: 18px/1.5em “proxima-nova”, Helvetica, Arial, sans-serif;
}
a { color: #069; }
h3 {
margin-left: 30px;
font: normal 26px “omnes-pro”, Helvetica, Arial, sans-serif;
color: #666;
}
p {
margin-top: 10px;
}
button {
font-size: 18px;
padding: 1px 7px;
}
input {
font-size: 18px;
}
input[type=checkbox] {
margin: 7px;
}
#header {
position: relative;
width: 900px;
margin: auto;
}
#header h2 {
margin-left: 10px;
vertical-align: middle;
font-size: 42px;
font-weight: bold;
text-decoration: none;
color: #000;
}
#content {
width: 880px;
margin: 0 auto;
padding: 10px;
}
#footer {
margin-top: 25px;
margin-bottom: 10px;
text-align: center;
font-size: 12px;
color: #999;
}
.demo-container {
float: right;
box-sizing: border-box;
width: 850px;
height: 450px;
padding: 20px 15px 15px 15px;
margin: 15px auto 30px auto;
border: 1px solid #ddd;
background: #fff;
background: linear-gradient(#f6f6f6 0, #fff 50px);
background: -o-linear-gradient(#f6f6f6 0, #fff 50px);
background: -ms-linear-gradient(#f6f6f6 0, #fff 50px);
background: -moz-linear-gradient(#f6f6f6 0, #fff 50px);
background: -webkit-linear-gradient(#f6f6f6 0, #fff 50px);
box-shadow: 0 3px 10px rgba(0,0,0,0.15);
-o-box-shadow: 0 3px 10px rgba(0,0,0,0.1);
-ms-box-shadow: 0 3px 10px rgba(0,0,0,0.1);
-moz-box-shadow: 0 3px 10px rgba(0,0,0,0.1);
-webkit-box-shadow: 0 3px 10px rgba(0,0,0,0.1);
}
.demo-placeholder {
width: 100%;
height: 100%;
font-size: 14px;
line-height: 1.2em;
}
.legend table {
border-spacing: 5px;
}
.header
{
float: top;
text-align: center
border: 1px solid black;
height: 100px;
}
.datas
{
float: left;
width: 300px;
border: 1px solid black;
position: absolute;
bottom: 0;
;
}
.graphtitledatas
{
float: left;
width: 300px;
}
.graphtitledisplay
{
float: right;
width: 850px;
}
h1 { text-align: center }
h2 { text-align: center }
`