mirror of
https://github.com/eliasstepanik/vdo.ninja.git
synced 2026-01-27 13:28:33 +00:00
Merge pull request #552 from jcalado/master
Speedtest graphs, data logging, UI revamp
This commit is contained in:
commit
d7c0e0cfcd
10
main.css
10
main.css
@ -92,6 +92,16 @@ button.grey {
|
|||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button.red {
|
||||||
|
-webkit-app-region: no-drag;
|
||||||
|
padding: 10px;
|
||||||
|
margin: 10px 0px;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 2px;
|
||||||
|
background-color: var(--red-accent);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
-webkit-app-region: no-drag;
|
-webkit-app-region: no-drag;
|
||||||
padding: 7px 10px 6px 10px;
|
padding: 7px 10px 6px 10px;
|
||||||
|
|||||||
119
speedtest.css
Normal file
119
speedtest.css
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
body {
|
||||||
|
background-color: #141926;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
color: white;
|
||||||
|
margin: 20px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 small {
|
||||||
|
display: block;
|
||||||
|
margin-top: 0.5em;
|
||||||
|
font-size: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#container, #graphs, #log {
|
||||||
|
width: 80%;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#explanation {
|
||||||
|
color: white;
|
||||||
|
font-family: sans-serif;
|
||||||
|
width: 80%;
|
||||||
|
margin: 0 auto;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#explanation h2 {
|
||||||
|
border-bottom: 1px solid #383838;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
iframe {
|
||||||
|
min-height: 30vh;
|
||||||
|
width: 39vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
#controls {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#controls button {
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas {
|
||||||
|
background-color: black;
|
||||||
|
margin: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#log {
|
||||||
|
margin-top: 20px;
|
||||||
|
background: #2a2a2a;
|
||||||
|
padding: 20px 0px;
|
||||||
|
border: 1px solid #383838;
|
||||||
|
}
|
||||||
|
|
||||||
|
#log ul {
|
||||||
|
margin: 20px;
|
||||||
|
list-style: none;
|
||||||
|
color: #cacaca;
|
||||||
|
max-height: 20vh;
|
||||||
|
overflow: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#graphs {
|
||||||
|
display: flex;
|
||||||
|
margin-top: 20px;
|
||||||
|
background: #2a2a2a;
|
||||||
|
padding: 20px 0px;
|
||||||
|
border: 1px solid #383838;
|
||||||
|
}
|
||||||
|
|
||||||
|
.graph {
|
||||||
|
flex: 1;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.graph h2, #log h2 {
|
||||||
|
margin: 0px 20px;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.graph > span {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 30px;
|
||||||
|
left: 30px;
|
||||||
|
color: #cacaca;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol {
|
||||||
|
margin-left: 20px;
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen
|
||||||
|
and (min-device-width: 375px)
|
||||||
|
and (max-device-width: 812px)
|
||||||
|
and (orientation: portrait) {
|
||||||
|
|
||||||
|
#container {
|
||||||
|
width: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#graphs {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
iframe {
|
||||||
|
width: 90vw;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#statsdiv {display: none;}
|
||||||
479
speedtest.html
479
speedtest.html
@ -1,192 +1,343 @@
|
|||||||
<html>
|
<html>
|
||||||
<head><title>OBSN Speed Test</title>
|
<head>
|
||||||
<style>
|
<link rel="stylesheet" href="./lineawesome/css/line-awesome.min.css" />
|
||||||
body{
|
<link rel="stylesheet" href="./main.css?ver=11" />
|
||||||
padding:0;
|
<link rel="stylesheet" href="./speedtest.css?ver=1" />
|
||||||
margin:0;
|
<meta charset="utf8" />
|
||||||
}
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
iframe {
|
<title>OBSN Speed Test</title>
|
||||||
border:0;
|
<script>
|
||||||
margin:0;
|
(function (w) {
|
||||||
padding:0;
|
w.URLSearchParams =
|
||||||
display:inline-block;
|
w.URLSearchParams ||
|
||||||
margin:0;
|
function (searchString) {
|
||||||
width:100%;
|
var self = this;
|
||||||
height:100%;
|
self.searchString = searchString;
|
||||||
}
|
self.get = function (name) {
|
||||||
span {
|
var results = new RegExp("[\?&]" + name + "=([^&#]*)").exec(
|
||||||
border:0;
|
self.searchString
|
||||||
margin:0;
|
);
|
||||||
padding:0;
|
if (results == null) {
|
||||||
display:inline-block;
|
return null;
|
||||||
margin:0;
|
} else {
|
||||||
width:50%;
|
return decodeURI(results[1]) || 0;
|
||||||
height:45%;
|
}
|
||||||
}
|
};
|
||||||
#viewlink {
|
};
|
||||||
width:400px;
|
})(window);
|
||||||
}
|
var urlParams = new URLSearchParams(window.location.search);
|
||||||
#container {
|
|
||||||
display:block;
|
|
||||||
padding:0px;
|
|
||||||
}
|
|
||||||
input{
|
|
||||||
padding:5px;
|
|
||||||
margin:5px;
|
|
||||||
}
|
|
||||||
button{
|
|
||||||
padding:5px;
|
|
||||||
margin:5px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<script>
|
|
||||||
|
|
||||||
(function (w) {
|
function loadIframe() {
|
||||||
w.URLSearchParams = w.URLSearchParams || function (searchString) {
|
// this is pretty important if you want to avoid camera permission popup problems. YOu need to load the iFRAME after you load the parent body. A quick solution is like: <body onload=>loadIframe();"> !!!
|
||||||
var self = this;
|
|
||||||
self.searchString = searchString;
|
|
||||||
self.get = function (name) {
|
|
||||||
var results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(self.searchString);
|
|
||||||
if (results == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return decodeURI(results[1]) || 0;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
})(window);
|
var streamID = "";
|
||||||
var urlParams = new URLSearchParams(window.location.search);
|
var possible =
|
||||||
|
"ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789";
|
||||||
|
for (var i = 0; i < 7; i++) {
|
||||||
|
streamID += possible.charAt(
|
||||||
|
Math.floor(Math.random() * possible.length)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function loadIframe(){ // this is pretty important if you want to avoid camera permission popup problems. YOu need to load the iFRAME after you load the parent body. A quick solution is like: <body onload=>loadIframe();"> !!!
|
var iframe = document.createElement("iframe");
|
||||||
|
var iframeContainer = document.createElement("span");
|
||||||
|
|
||||||
var streamID = "";
|
iframe.allow = "autoplay";
|
||||||
var possible = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789";
|
var srcString =
|
||||||
for (var i = 0; i < 7; i++){
|
"./?push=" +
|
||||||
streamID += possible.charAt(Math.floor(Math.random() * possible.length));
|
streamID +
|
||||||
}
|
"&cleanoutput&privacy&webcam&audiodevice=0&fullscreen";
|
||||||
|
|
||||||
|
if (urlParams.has("turn")) {
|
||||||
|
srcString = srcString + "&turn=" + urlParams.get("turn");
|
||||||
|
}
|
||||||
|
|
||||||
var iframe = document.createElement("iframe");
|
// we are changing some text on page load, just to demonstrate what's possible.
|
||||||
var iframeContainer = document.createElement("span");
|
iframe.onload = function (e) {
|
||||||
|
e.target.contentWindow.postMessage(
|
||||||
|
{
|
||||||
|
function: "changeHTML",
|
||||||
|
target: "add_camera",
|
||||||
|
value: "Select your Camera",
|
||||||
|
},
|
||||||
|
"*"
|
||||||
|
);
|
||||||
|
};
|
||||||
|
iframe.src = srcString;
|
||||||
|
|
||||||
iframe.allow="autoplay";
|
iframeContainer.appendChild(iframe);
|
||||||
var srcString = "./?push="+streamID+"&cleanoutput&privacy&webcam&audiodevice=0&fullscreen";
|
document.getElementById("container").appendChild(iframeContainer);
|
||||||
|
|
||||||
if (urlParams.has('turn')){
|
var iframe = document.createElement("iframe");
|
||||||
srcString = srcString+"&turn="+urlParams.get("turn");
|
var iframeContainer = document.createElement("span");
|
||||||
}
|
|
||||||
|
|
||||||
// we are changing some text on page load, just to demonstrate what's possible.
|
iframe.allow = "autoplay";
|
||||||
iframe.onload = function(e){e.target.contentWindow.postMessage({"function":"changeHTML", "target":"add_camera","value":"Select your Camera"}, '*');};
|
var srcString = "./?view=" + streamID + "&cleanoutput&privacy&noaudio";
|
||||||
iframe.src = srcString;
|
|
||||||
|
|
||||||
iframeContainer.appendChild(iframe);
|
if (urlParams.has("turn")) {
|
||||||
document.getElementById("container").appendChild(iframeContainer);
|
srcString = srcString + "&turn=" + urlParams.get("turn");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (urlParams.has("buffer")) {
|
||||||
|
srcString = srcString + "&buffer=" + urlParams.get("buffer");
|
||||||
|
}
|
||||||
|
|
||||||
|
iframe.src = srcString;
|
||||||
|
|
||||||
|
iframeContainer.appendChild(iframe);
|
||||||
|
document.getElementById("container").appendChild(iframeContainer);
|
||||||
|
|
||||||
|
var button = document.createElement("br");
|
||||||
|
document.getElementById("container").appendChild(button);
|
||||||
|
|
||||||
|
var buttonContainer = document.createElement("div");
|
||||||
|
buttonContainer.id = "controls";
|
||||||
|
|
||||||
|
var button = document.createElement("button");
|
||||||
|
button.innerHTML = "Disconnect";
|
||||||
|
button.className = "red";
|
||||||
|
button.onclick = function () {
|
||||||
|
iframe.contentWindow.postMessage({ close: true }, "*");
|
||||||
|
};
|
||||||
|
buttonContainer.appendChild(button);
|
||||||
|
|
||||||
|
var button = document.createElement("button");
|
||||||
|
button.innerHTML = "Low Bitrate";
|
||||||
|
button.className = "grey";
|
||||||
|
button.onclick = function () {
|
||||||
|
iframe.contentWindow.postMessage({ bitrate: 30 }, "*");
|
||||||
|
};
|
||||||
|
buttonContainer.appendChild(button);
|
||||||
|
|
||||||
|
var button = document.createElement("button");
|
||||||
|
button.innerHTML = "High Bitrate";
|
||||||
|
button.className = "grey";
|
||||||
|
button.onclick = function () {
|
||||||
|
iframe.contentWindow.postMessage({ bitrate: 6000 }, "*");
|
||||||
|
};
|
||||||
|
buttonContainer.appendChild(button);
|
||||||
|
|
||||||
|
var button = document.createElement("button");
|
||||||
|
button.innerHTML = "Default Bitrate";
|
||||||
|
button.className = "grey";
|
||||||
|
button.onclick = function () {
|
||||||
|
iframe.contentWindow.postMessage({ bitrate: -1 }, "*");
|
||||||
|
};
|
||||||
|
buttonContainer.appendChild(button);
|
||||||
|
|
||||||
|
document.getElementById("container").appendChild(buttonContainer);
|
||||||
|
|
||||||
|
setInterval(function () {
|
||||||
|
iframe.contentWindow.postMessage({ getStats: true }, "*");
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
var eventMethod = window.addEventListener
|
||||||
|
? "addEventListener"
|
||||||
|
: "attachEvent";
|
||||||
|
var eventer = window[eventMethod];
|
||||||
|
var messageEvent =
|
||||||
|
eventMethod === "attachEvent" ? "onmessage" : "message";
|
||||||
|
var previousResolution;
|
||||||
|
|
||||||
|
eventer(messageEvent, function (e) {
|
||||||
|
if ("action" in e.data) {
|
||||||
|
logData("Action",e.data.action);
|
||||||
|
}
|
||||||
|
if ("stats" in e.data) {
|
||||||
|
var out = "";
|
||||||
|
for (var streamID in e.data.stats.inbound_stats) {
|
||||||
|
out += printValues(e.data.stats.inbound_stats[streamID]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (out.split("Bitrate_in_kbps").length > 1) {
|
||||||
|
|
||||||
var iframe = document.createElement("iframe");
|
for (var key in e.data.stats.inbound_stats[streamID]) {
|
||||||
var iframeContainer = document.createElement("span");
|
|
||||||
|
|
||||||
iframe.allow="autoplay";
|
if (key.startsWith("RTCMediaStreamTrack_receiver")) {
|
||||||
var srcString = "./?view="+streamID+"&cleanoutput&privacy&noaudio";
|
var bitrate =
|
||||||
|
e.data.stats.inbound_stats[streamID][key][
|
||||||
|
"Bitrate_in_kbps"
|
||||||
|
];
|
||||||
|
plotData("bitrate-graph", bitrate, 6000);
|
||||||
|
|
||||||
if (urlParams.has('turn')){
|
var buffer =
|
||||||
srcString = srcString+"&turn="+urlParams.get("turn");
|
e.data.stats.inbound_stats[streamID][key][
|
||||||
}
|
"Buffer_Delay_in_ms"
|
||||||
|
];
|
||||||
|
plotData("buffer-graph", buffer, 200);
|
||||||
|
|
||||||
if (urlParams.has('buffer')){
|
var packetloss =
|
||||||
srcString = srcString+"&buffer="+urlParams.get("buffer");
|
e.data.stats.inbound_stats[streamID][key][
|
||||||
}
|
"packetLoss_in_percentage"
|
||||||
|
].toFixed(2);
|
||||||
|
plotData("packetloss-graph", packetloss, 3);
|
||||||
|
|
||||||
iframe.src = srcString;
|
var resolution =
|
||||||
|
e.data.stats.inbound_stats[streamID][key][
|
||||||
|
"Resolution"
|
||||||
|
]
|
||||||
|
|
||||||
iframeContainer.appendChild(iframe);
|
if(previousResolution != resolution) {
|
||||||
document.getElementById("container").appendChild(iframeContainer);
|
previousResolution = resolution;
|
||||||
|
logData("Resolution", resolution);
|
||||||
|
}
|
||||||
|
|
||||||
var button = document.createElement("br");
|
}
|
||||||
document.getElementById("container").appendChild(button);
|
}
|
||||||
|
|
||||||
var button = document.createElement("button");
|
document.getElementById("statsdiv").innerHTML =
|
||||||
button.innerHTML = "Disconnect";
|
"<b>Bitrate (Kbps)</b>" + out.split("Bitrate_in_kbps")[1];
|
||||||
button.onclick = function(){iframe.contentWindow.postMessage({"close":true}, '*');}
|
}
|
||||||
document.getElementById("container").appendChild(button);
|
}
|
||||||
|
});
|
||||||
var button = document.createElement("button");
|
|
||||||
button.innerHTML = "Low Bitrate";
|
|
||||||
button.onclick = function(){iframe.contentWindow.postMessage({"bitrate":30}, '*');}
|
|
||||||
document.getElementById("container").appendChild(button);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var button = document.createElement("button");
|
|
||||||
button.innerHTML = "High Bitrate";
|
|
||||||
button.onclick = function(){iframe.contentWindow.postMessage({"bitrate":6000}, '*');}
|
|
||||||
document.getElementById("container").appendChild(button);
|
|
||||||
|
|
||||||
var button = document.createElement("button");
|
|
||||||
button.innerHTML = "Default Bitrate";
|
|
||||||
button.onclick = function(){iframe.contentWindow.postMessage({"bitrate":-1}, '*');}
|
|
||||||
document.getElementById("container").appendChild(button);
|
|
||||||
|
|
||||||
setInterval(function(){iframe.contentWindow.postMessage({"getStats":true}, '*');},1000);
|
|
||||||
|
|
||||||
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
|
|
||||||
var eventer = window[eventMethod];
|
|
||||||
var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
|
|
||||||
|
|
||||||
eventer(messageEvent, function (e) {
|
|
||||||
|
|
||||||
if ("stats" in e.data){
|
|
||||||
var out = "";
|
|
||||||
|
|
||||||
for (var streamID in e.data.stats.inbound_stats){
|
|
||||||
out += printValues(e.data.stats.inbound_stats[streamID]);
|
|
||||||
}
|
}
|
||||||
if (out.split("Bitrate_in_kbps").length>1){
|
|
||||||
document.getElementById("statsdiv").innerHTML = "<b>Bitrate_in_kbps</b>"+out.split("Bitrate_in_kbps")[1];
|
function printValues(obj) {
|
||||||
|
var out = "";
|
||||||
|
for (var key in obj) {
|
||||||
|
if (typeof obj[key] === "object") {
|
||||||
|
out += "<br />";
|
||||||
|
out += printValues(obj[key]);
|
||||||
|
} else {
|
||||||
|
if (key.startsWith("_")) {
|
||||||
|
} else {
|
||||||
|
out += "<b>" + key + "</b>: " + obj[key] + "<br />";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
function logData(type, data){
|
||||||
}
|
var log = document.getElementById("log").getElementsByTagName("ul")[0];
|
||||||
|
var entry = document.createElement('li');
|
||||||
function printValues( obj) {
|
entry.innerText = "[" + new Date().toLocaleTimeString() + "] " + type + " : " + data;
|
||||||
var out = "";
|
log.prepend(entry);
|
||||||
for (var key in obj) {
|
|
||||||
if (typeof obj[key] === "object") {
|
|
||||||
out +="<br />";
|
|
||||||
out += printValues(obj[key]);
|
|
||||||
} else {
|
|
||||||
if (key.startsWith("_")){} else {
|
|
||||||
out +="<b>"+key+"</b>: "+obj[key]+"<br />";
|
|
||||||
}
|
}
|
||||||
}
|
</script>
|
||||||
}
|
</head>
|
||||||
return out;
|
<body onload="loadIframe();">
|
||||||
}
|
<div id="header">
|
||||||
|
<a
|
||||||
|
id="logoname"
|
||||||
|
href="./"
|
||||||
|
style="text-decoration: none; color: white; margin: 2px"
|
||||||
|
>
|
||||||
|
<span data-translate="logo-header">
|
||||||
|
<font id="qos">O</font>BS.Ninja
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div id="container">
|
||||||
|
<h1>
|
||||||
|
OBS.Ninja Speed Test - prototype version
|
||||||
|
<small>(Tests connection to TURN server and back)</small>
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
<div id="graphs">
|
||||||
|
<div class="graph">
|
||||||
|
<h2>Bitrate (kbps)</h2>
|
||||||
|
<span>0</span>
|
||||||
|
<canvas id="bitrate-graph"></canvas>
|
||||||
|
</div>
|
||||||
|
|
||||||
</script>
|
<div class="graph">
|
||||||
</head>
|
<h2>Buffer delay (ms)</h2>
|
||||||
<body onload="loadIframe();">
|
<span>0</span>
|
||||||
<div id="container">
|
<canvas id="buffer-graph"></canvas>
|
||||||
</div>
|
</div>
|
||||||
<div style="width:48%;padding:0;margin:0;border:0;display:inline-block;">
|
|
||||||
<h3>OBS.Ninja Speed Test - prototype version</h3>
|
<div class="graph">
|
||||||
(Tests connection to TURN server and back)<br /><br />
|
<h2>Packet Loss (%)</h2>
|
||||||
<li>1.Select your camera.</li>
|
<span>0</span>
|
||||||
<li>2.Hit start</li>
|
<canvas
|
||||||
<li>3.Wait for the video to load side-by-side. *If it does not auto-load within 20s, refresh and try again.*</li>
|
id="packetloss-graph"
|
||||||
<li>4.Stats will load on the right-hand side of the page here. (or press CTRL + LeftClick on the new video to open stats that way)</li>
|
></canvas>
|
||||||
<li>5.Bitrate_in_kbps, Buffer_Delay_in_ms, and packetLoss_percentage are important connection quality metrics</li>
|
</div>
|
||||||
<li>6.Increase the video bitrate by pressing <i>High Bitrate</i>; it should approach 6000-kbps if the network allows.</li>
|
</div>
|
||||||
</div>
|
<div id="log">
|
||||||
<div id="statsdiv" style="width:48%;padding:0;margin:0;border:0;display:inline-block;">
|
<h2>Log</h2>
|
||||||
</div>
|
<ul></ul>
|
||||||
</body>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div id="explanation">
|
||||||
|
<h2>How to use</h2>
|
||||||
|
<ol>
|
||||||
|
<li>Select your camera.</li>
|
||||||
|
<li>Hit start</li>
|
||||||
|
<li>
|
||||||
|
Wait for the video to load side-by-side. *If it does not auto-load
|
||||||
|
within 20s, refresh and try again.*
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Stats will load on the right-hand side of the page here. (or press
|
||||||
|
CTRL + LeftClick on the new video to open stats that way)
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Bitrate, Buffer delay, and packet loss are
|
||||||
|
important connection quality metrics
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Change the video bitrate by pressing the buttons below the video.
|
||||||
|
It should approach 6000-kbps if the network allows.
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
<div id="statsdiv"></div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function plotData(element, data, max) {
|
||||||
|
var canvas;
|
||||||
|
var context;
|
||||||
|
var yScale;
|
||||||
|
|
||||||
|
canvas = document.getElementById(element);
|
||||||
|
context = canvas.getContext("2d");
|
||||||
|
|
||||||
|
if (isNaN(data)) {
|
||||||
|
data = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var text = (canvas.previousElementSibling.innerHTML = data);
|
||||||
|
|
||||||
|
var height = context.canvas.height;
|
||||||
|
var width = context.canvas.width;
|
||||||
|
|
||||||
|
context.fillStyle = "#009933";
|
||||||
|
context.imageSmoothingEnabled = true;
|
||||||
|
|
||||||
|
yScale = height / max;
|
||||||
|
|
||||||
|
if (data > max) {
|
||||||
|
data = max;
|
||||||
|
}
|
||||||
|
|
||||||
|
context.fillRect(
|
||||||
|
width - 1,
|
||||||
|
height - data * yScale,
|
||||||
|
1,
|
||||||
|
height
|
||||||
|
);
|
||||||
|
|
||||||
|
// shift everything to the left:
|
||||||
|
var imageData = context.getImageData(
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
width - 1,
|
||||||
|
height
|
||||||
|
);
|
||||||
|
context.putImageData(imageData, 0, 0);
|
||||||
|
// now clear the right-most pixels:
|
||||||
|
context.clearRect(
|
||||||
|
width - 1,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
height
|
||||||
|
);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
Loading…
x
Reference in New Issue
Block a user