I am using FabricJS to put SVG objects on Canvas element in the HTML.
But since the FabricJS uses new keyword to instantiate classes, I think the properties of that class are getting tied to the global namespace.
Below, is my code for reference
My JSON object that I am parsing
var defaultSceneObj = [
{
"eyes": "res/img/animals/cat/cat_part_eye.svg",
"skin": "res/img/animals/cat/cat_skin.svg",
"mouth": "res/img/animals/cat/cat_part_mouth_happy.svg",
"pos": {
"ground" : "right_back",
"sky" : "none", //other values ["none"]
"relative" : "none" //other values ["none", "top", "bottom"]
}
},
{
"eyes": "res/img/animals/cat/cat_part_eye.svg",
"skin": "res/img/animals/cat/cat_skin.svg",
"mouth": "res/img/animals/cat/cat_part_mouth_happy.svg",
"pos": {
"ground" : "left_back",
"sky" : "none", //other values ["none"]
"relative" : "none" //other values ["none", "top", "bottom"]
}
}
];
Which means there are 2 animals in my object, where each animal is composed of eye, skin and mouth svg files.
I am looping through them in my javascript code to render them
var renderObjOnCanvas = function(cObj, cDim){
// console.log("Object, Dimension:", cObj, cDim);
// var canvas = new fabric.Canvas('elem-frame-svg');
var canvas = this.__canvas = new fabric.Canvas('elem-frame-svg');
imgwidth = 200; //default image width
imgheight = 255; //default image height
imgScale = 0.6;
imgOffsetX = Math.floor(imgwidth*imgScale/2);
imgOffsetY = Math.floor(imgheight*imgScale/2);
canvaswidth = canvas.width;
canvasheight = canvas.height;
// console.log("render canvas dimensions:", canvaswidth, canvasheight);
if (cObj.length > 0){
for (var c =0; c < cObj.length; c++){
var noun = cObj[c]; //assign the noun object
if (noun.skin !== 'Undefined'){
var animalParts = ['skin', 'eyes', 'mouth'];
var pos = cDim.ground[noun.pos.ground];
for (var g = 0; g < animalParts.length; g++){
var part_top = canvasheight - (pos[1] + imgOffsetY);
var part_left = pos[0] - imgOffsetX;
console.log("part:", noun[animalParts[g]], "part_position: ", part_top, part_left);
var img = new fabric.Image.fromURL(noun[animalParts[g]], function(s){
this.top = part_top;
this.left = part_left;
// this.scale(imgScale);
s = this;
console.log("part:", part_top, part_left);
canvas.add();
});
}
}
}
}
};
The first console.log outputs the correct top and left coordinates, but the second one only outputs the last values assigned and hence all my objects are getting placed on the same position in canvas.
Output for the first console.log:
part: res/img/animals/cat/cat_skin.svg part_position: 282 574 main.js:126
part: res/img/animals/cat/cat_part_eye.svg part_position: 282 574 main.js:126
part: res/img/animals/cat/cat_part_mouth_happy.svg part_position: 282 574 main.js:126
part: res/img/animals/cat/cat_skin.svg part_position: 282 135 main.js:126
part: res/img/animals/cat/cat_part_eye.svg part_position: 282 135 main.js:126
part: res/img/animals/cat/cat_part_mouth_happy.svg part_position: 282 135
Output for the second console.log:
(6) part: 282 135
It's because the forEach manages the scope for the variables for you and whereas for does not. For example,
var arr = [1,2,3];
for (var i=0;i<arr.length;i++){
var r = 100;
}
console.log(r); //prints 100
arr.forEach(function(){
var w = 100;
});
console.log(w); //prints "w is not defined"
so in your case, the part_top, part_left variables exists outside the for loop and only the last assigned value will be taken up by the call back function as variables are passed by reference. Take a look this answer
scope of variables in JavaScript callback functions
Using forEach() method instead of for loop worked for me. Although I am not sure, why
Our closest explanation is that since forEach() accepts an anonymous function, it binds the scope of variables into a closure when the new operator is invoked.
...
if (noun.skin !== 'Undefined'){
var animalParts = ['skin', 'eyes', 'mouth'];
var pos = cDim.ground[noun.pos.ground];
animalParts.forEach(function(item, g){ // <-works
// for (var g = 0; g < animalParts.length; g++){
var part_top = canvasheight - (pos[1] + imgOffsetY);
var part_left = pos[0] - imgOffsetX;
console.log("part:", noun[animalParts[g]], "part_position: ", part_top, part_left);
var img = new fabric.Image.fromURL(noun[animalParts[g]], function(s){
s.top = part_top;
s.left = part_left;
s.scale(imgScale);
// console.log(s, s.top,s.left, part_top, part_left);
canvas.add(s);
});
});
}
...
Related
var firstExample = Object.create(potentialEnergy)
firstExample.height = 200
firstExample.mass = 85
var secondExample = Object.create(potentialEnergy)
secondExample.height = 150
secondExample.mass = 100
var thirdExample = Object.create(potentialEnergy)
thirdExample.height = 250
thirdExample.mass = 75
// object method
var potentialEnergy = {
getPE: function () {
const potential = this.mass * this.height * 9.8
return potential
}
}
console.log(firstExample.getPE())
console.log(secondExample.getPE())
console.log(thirdExample.getPE())
question: In my first attempt I created three objects w/ properties of mass and height using the given notation (var firstExample = {}, and the properties below)and then (seperate line of code) I attempted to link the potential energy method with the firstExample object via -->
var firstExample = Object.create(potentialEnergy) and this returned NaN,
I then did Object.create(potentialEnergy) when I created all three objects and the program worked,
my question is are firstExample (and second/third) being made into objects or just variables, because I am currently not using either of the methods I was taught(var firstExample = {} , or open brackets {with the properties within}), and if they are being made into objects then does "Object.create" both link the potentialEnergy method with firstExample and* make firstExample into an object?
You can try to change the declaration's order:
So, it will be:
// object method
var potentialEnergy = {
getPE: function () {
const potential = this.mass * this.height * 9.8
return potential
}
}
var firstExample = Object.create(potentialEnergy)
firstExample.height = 200
firstExample.mass = 85
var secondExample = Object.create(potentialEnergy)
secondExample.height = 150
secondExample.mass = 100
var thirdExample = Object.create(potentialEnergy)
thirdExample.height = 250
thirdExample.mass = 75
console.log(firstExample.getPE())
console.log(secondExample.getPE())
console.log(thirdExample.getPE())
I have been working on an issue for a few days now and have been unable to solve it. Please note that I am relatively new to Javascript so not sure if what I have below is the best way to accomplish this but going through this over the last few days has definitely helped me learn some new things.
Also, please note I know I could achieve this very easily using CSS but I wanted to know if there was a Javascript/JQuery solution.
The Issue:
I am attempting to simulate a fadeIn animation on a canvas for some text.
var introText =
{
"Welcome To A New Day...": "75",
"Full Service Web Design": "50",
"by": "50",
"J. David Hock": "50"
};
The numbers represent font size.
Here is the full code:
$(document).ready(function ()
{
var canvas = $('#myCanvas')[0];
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var ctx = canvas.getContext('2d');
var introText =
{
"Welcome To A New Day...": "75",
"Full Service Web Design": "50",
"by": "50",
"J. David Hock": "50"
};
function fadeText(timeStamp, t, x, y)
{
var opacity = timeStamp / 1000;
console.log('Timestamp: ' + timeStamp + ' Opacity: ' + opacity);
console.log('t, x, y |' + t +' | ' + x + ' | ' + y)
//ctx.clearRect(0, 0, canvas.width, canvas.height);
//ctx.fillStyle = 'rgba(178, 34, 34, ' + opacity + ')';
//ctx.fillText(t, x, y);
if (opacity < 1)
{
requestAnimationFrame(function (timestamp)
{
fadeText(timestamp, t, x, y)
});
}
}
function MessageObject(x, y, f, fs, t)
{
this.x = x;
this.y = y;
this.f = f;
this.fs = fs;
this.t = t;
}
var msgArray = [];
function CreateMessageArray(myArray, callback)
{
var i = 0;
var v = 75;
var x = 0;
var y = 0;
var f = '';
var fs = '';
var t = '';
for (t in myArray)
{
fs = myArray[t]; //font size
f = 'italic ' + fs + 'px Bradley Hand'; //font type
x = (canvas.width / 2) //x pos of text
msgArray.push(new MessageObject(x, y, f, fs, t))
y = Number(fs);
//alert('x, y, f, t | ' + x + ' | ' + y + ' | ' + f + ' | ' + t);
}
return callback(msgArray);
}
let ProcessMessageArray = function (myArray)
{
var xPrime = 0;
var yPrime = 0;
for (i = 0; i < myArray.length; i++)
{
var msgObject = myArray[i];
var x = msgObject.x;
var y = msgObject.y;
var f = msgObject.f;
var fs = msgObject.fs;
var t = msgObject.t;
ctx.textBaseline = 'top';
ctx.font = f;
var txtWidth = ctx.measureText(t).width
xPrime = x - (txtWidth / 2);
requestAnimationFrame(function(timestamp)
{
fadeText(timestamp, t, x, y)
});
//ctx.fillStyle = 'rgba(178, 34, 34, 1)';
//ctx.fillText(t, xPrime, yPrime);
if (i === 0)
{
yPrime = Number(yPrime) + (2 * Number(fs) - 35);
}
else
{
yPrime = Number(yPrime) + Number(fs);
}
}
}
CreateMessageArray(introText, ProcessMessageArray)
});
The way it is supposed to work is that the CreateMessageArray function creates an array of objects that contain the x-pos, y-pos, etc. for each of the lines of text in the introTextArray.
The ProcessMessageArray is then responsible for outputting each line of text in the introTextArray into it proper position on the screen.
In the ProcessMessageArray there is a call to a requestAnimationFrame function where I was hoping it would "fade in" each line of the text but what is actually occurring is that I am only getting the last line of text in the IntroTextArray.
I am sure it has to do with the fact that I am calling a requestAnimationFrame within a loop but I am not sure how to accomplish what I want to otherwise. Any advice would be appreciated.
Animating with requestAnimationFrame
RAF requestAnimationFrame
There are many ways that you can use RAF to animate. You can have many RAF functions that will all present the content to the display at the same time if they are all called before the next refresh. But this does incur extra overhead (especially if the rendering load per item is small) and extra head pain. If you have a slow render (for whatever reason, browsers can hang for a 1/60th second without warning) it can mess up the presentation order and you will only get part of the frame rendered.
The easiest way to manage any type of animation is to have a single animation loop that is called via RAF. From that function you call your animations. This is safe from presentation order problems, and can easily switch state. (eg after text fade you may want something else fading in)
Fix
Your code was not able to be saved, and sorry, was DOA, so I rewrote the whole thing. I have added some notes in the comments as to why and what for but if you have questions please do ask in the comments.
The example displays the fading text as two sets (to illustrate changing display state, a state is an abstract and reference to related elements or render (eg intro, middle, end))
The function has one main loop that handles the rendering calls and the timing.
Text is displayed by a function that updates and then displays. it will return true when text has faded in so main loop can set up the next render state.
// Removed jQuery and I dont see the need to use it
// The convention in JavaScript is NOT to capitalize the first character
// unless you function / object is created using new ObjectName
const ctx = canvas.getContext("2d"); // canvas is named in HTML via its id
canvas.width = innerWidth; // window is the global scope you do not need
canvas.height = innerHeight; //to prefix when accesing its properties
// helper
//const log = (...data) => console.log(data.join(","));
// use an array it is better suited to the data you are storing
const introText = [
["Testing one two.", 75],
["Testing..." , 50],
["One, one two.", 50],
["Is this thing on?", 50],
["",1], // to delay next state
["",1], // to delay next state
];
const introText1 = [
["Second stage, state 2", 20],
["The End" , 40],
[":)", 30],
["",10],
["Thanks for watching..",12],
["",1],
["",1],
["Dont forget to vote for any answers you find helpfull..",10],
["",1],
["",10],
["This intro was brought to you by",12],
["",10],
["requestAnimationFrame",12],
["HTML5",12],
["canvas",12],
["JavaScript (AKA) ECMAScript6",12],
["",10],
["Staring...",14],
["Stackoverflow.com",18],
];
const TEXT_FADE_TIME = 1000; // in ms
// create the array of display arrays
const displayLists = [
createDisplayList(8, introText), // converts simple text array to display list
createDisplayList(8, introText1),
];
var curretDisplayListIdx = 0;
requestAnimationFrame(mainLoop); // will start when all code has been parsed
var startTime;
function mainLoop(time){ // keep it simple use one animation frame per frame
if(startTime === undefined) { startTime = time }
const timeSinceStart = time - startTime;
ctx.clearRect(0,0, canvas.width, canvas.height);
if (textFadeIn(timeSinceStart, curretDisplayListIdx )) {
if (curretDisplayListIdx < displayLists.length - 1) {
curretDisplayListIdx += 1;
startTime = time;
}
}
requestAnimationFrame(mainLoop);
}
// creates a display list from text array. top is the start y pos
function createDisplayList(top, array) {
const result = [];
var y = top;
var fontSize;
for (const item of array) {
const fontSize = item[1];
result.push({
font : 'italic ' + fontSize + 'px Bradley Hand',
text : item[0],
x : canvas.width / 2,
y , fontSize,
startTime : null,
fadeTime : TEXT_FADE_TIME,
alpha : 0, // starting alpha
});
y += fontSize;
}
return result;
}
// displays a text display list from the displayLists array.
// time is time since starting to display the list
// displayListIdx is the index
// returns true when text has faded in
function textFadeIn(time, displayListIdx) {
// complete will be true when all have faded in
const complete = updateDisplayList(displayLists[displayListIdx], time);
renderDisplayList(displayLists[displayListIdx]);
return complete;
}
// separate logic from rendering
function updateDisplayList(array, time) {
var fadeNext = true; // used to indicate that the next item should fade in
// for each item
for (const text of array) {
if (fadeNext) { // has the previous items done its thing?
if (text.startTime === null) { text.startTime = time }
}
if(text.startTime !== null){ // if start time is set then fade it in
text.alpha = Math.min(1, (time - text.startTime) / text.fadeTime);
if (text.alpha < 1) { fadeNext = false } // if not complete flag fadeNext to stop next text fading in
}
}
// if last item is fully faded in return true
return array[array.length - 1].alpha === 1;
}
// seperate rendering from logic
function renderDisplayList(array) {
ctx.textBaseline = "top";
ctx.textAlign = "center";
ctx.fillStyle = "black";
for (const text of array) {
ctx.font = text.font;
ctx.globalAlpha = text.alpha;
ctx.fillText(text.text,text.x,text.y);
}
}
canvas {
position: absolute;
top: 0px;
left: 0px;
}
<canvas id="canvas"></canvas>
I have a database that has got a month full of datasets in 10min intervals. (So a dataset for every 10min)
Now I want to show that data on three graphs: last 24 hours, last 7 days and last 30 days.
The data looks like this:
{ "data" : 278, "date" : ISODate("2016-08-31T01:51:05.315Z") }
{ "data" : 627, "date" : ISODate("2016-08-31T01:51:06.361Z") }
{ "data" : 146, "date" : ISODate("2016-08-31T01:51:07.938Z") }
// etc
For the 24h graph I simply output the data for the last 24h, that's easy.
For the other graphs I thin the data:
const data = {}; //data from database
let newData = [];
const interval = 7; //for 7 days the interval is 7, for 30 days it's 30
for( let i = 0; i < data.length; i += interval ) {
newData.push( data[ i ] );
};
This works fine but extreme events where data is 0 or differs greatly from the other values average, can be lost depending on what time you search the data. Not thinning out the data however will result in a large sum of data points that are sent over the pipe and have to be processed on the front end. I'd like to avoid that.
Now to my question
How can I reduce the data for a 7 day period while keeping extremes in it? What's the most efficient way here?
Additions:
In essence I think I'm trying to simplify a graph to reduce points but keep the overall shape. (If you look at it from a pure image perspective)
Something like an implementation of Douglas–Peucker algorithm in node?
As you mention in the comments, the Ramer-Douglas-Peucker (RDP) algorithm is used to process data points in 2D figures but you want to use it for graph data where X values are fixed. I modified this Javascript implementation of the algorithm provided by M Oehm to consider only the vertical (Y) distance in the calculations.
On the other hand, data smoothing is often suggested to reduce the number of data points in a graph (see this post by csgillespie).
In order to compare the two methods, I made a small test program. The Reset button creates new test data. An algorithm can be selected and applied to obtain a reduced number of points, separated by the specified interval. In the case of the RDP algorithm however, the resulting points are not evenly spaced. To get the same number of points as for the specified interval, I run the calculations iteratively, adjusting the espilon value each time until the correct number of points is reached.
From my tests, the RDP algorithm gives much better results. The only downside is that the spacing between points varies. I don't think that this can be avoided, given that we want to keep the extreme points which are not evenly distributed in the original data.
Here is the code snippet, which is better seen in Full Page mode:
var svgns = 'http://www.w3.org/2000/svg';
var graph = document.getElementById('graph1');
var grpRawData = document.getElementById('grpRawData');
var grpCalculatedData = document.getElementById('grpCalculatedData');
var btnReset = document.getElementById('btnReset');
var cmbMethod = document.getElementById('cmbMethod');
var btnAddCalculated = document.getElementById('btnAddCalculated');
var btnClearCalculated = document.getElementById('btnClearCalculated');
var data = [];
var calculatedCount = 0;
var colors = ['black', 'red', 'green', 'blue', 'orange', 'purple'];
var getPeriod = function () {
return parseInt(document.getElementById('txtPeriod').value, 10);
};
var clearGroup = function (grp) {
while (grp.lastChild) {
grp.removeChild(grp.lastChild);
}
};
var showPoints = function (grp, pts, markerSize, color) {
var i, point;
for (i = 0; i < pts.length; i++) {
point = pts[i];
var marker = document.createElementNS(svgns, 'circle');
marker.setAttributeNS(null, 'cx', point.x);
marker.setAttributeNS(null, 'cy', point.y);
marker.setAttributeNS(null, 'r', markerSize);
marker.setAttributeNS(null, 'fill', color);
grp.appendChild(marker);
}
};
// Create and display test data
var showRawData = function () {
var i, x, y;
var r = 0;
data = [];
for (i = 1; i < 500; i++) {
x = i;
r += 15.0 * (Math.random() * Math.random() - 0.25);
y = 150 + 30 * Math.sin(x / 200) * Math.sin((x - 37) / 61) + 2 * Math.sin((x - 7) / 11) + r;
data.push({ x: x, y: y });
}
showPoints(grpRawData, data, 1, '#888');
};
// Gaussian kernel smoother
var createGaussianKernelData = function () {
var i, x, y;
var r = 0;
var result = [];
var period = getPeriod();
for (i = Math.floor(period / 2) ; i < data.length; i += period) {
x = data[i].x;
y = gaussianKernel(i);
result.push({ x: x, y: y });
}
return result;
};
var gaussianKernel = function (index) {
var halfRange = Math.floor(getPeriod() / 2);
var distance, factor;
var totalValue = 0;
var totalFactor = 0;
for (i = index - halfRange; i <= index + halfRange; i++) {
if (0 <= i && i < data.length) {
distance = Math.abs(i - index);
factor = Math.exp(-Math.pow(distance, 2));
totalFactor += factor;
totalValue += data[i].y * factor;
}
}
return totalValue / totalFactor;
};
// Ramer-Douglas-Peucker algorithm
var ramerDouglasPeuckerRecursive = function (pts, first, last, eps) {
if (first >= last - 1) {
return [pts[first]];
}
var slope = (pts[last].y - pts[first].y) / (pts[last].x - pts[first].x);
var x0 = pts[first].x;
var y0 = pts[first].y;
var iMax = first;
var max = -1;
var p, dy;
// Calculate vertical distance
for (var i = first + 1; i < last; i++) {
p = pts[i];
y = y0 + slope * (p.x - x0);
dy = Math.abs(p.y - y);
if (dy > max) {
max = dy;
iMax = i;
}
}
if (max < eps) {
return [pts[first]];
}
var p1 = ramerDouglasPeuckerRecursive(pts, first, iMax, eps);
var p2 = ramerDouglasPeuckerRecursive(pts, iMax, last, eps);
return p1.concat(p2);
}
var internalRamerDouglasPeucker = function (pts, eps) {
var p = ramerDouglasPeuckerRecursive(data, 0, pts.length - 1, eps);
return p.concat([pts[pts.length - 1]]);
}
var createRamerDouglasPeuckerData = function () {
var finalPointCount = Math.round(data.length / getPeriod());
var epsilon = getPeriod();
var pts = internalRamerDouglasPeucker(data, epsilon);
var iteration = 0;
// Iterate until the correct number of points is obtained
while (pts.length != finalPointCount && iteration++ < 20) {
epsilon *= Math.sqrt(pts.length / finalPointCount);
pts = internalRamerDouglasPeucker(data, epsilon);
}
return pts;
};
// Event handlers
btnReset.addEventListener('click', function () {
calculatedCount = 0;
clearGroup(grpRawData);
clearGroup(grpCalculatedData);
showRawData();
});
btnClearCalculated.addEventListener('click', function () {
calculatedCount = 0;
clearGroup(grpCalculatedData);
});
btnAddCalculated.addEventListener('click', function () {
switch (cmbMethod.value) {
case "Gaussian":
showPoints(grpCalculatedData, createGaussianKernelData(), 2, colors[calculatedCount++]);
break;
case "RDP":
showPoints(grpCalculatedData, createRamerDouglasPeuckerData(), 2, colors[calculatedCount++]);
return;
}
});
showRawData();
div
{
margin-bottom: 6px;
}
<div>
<button id="btnReset">Reset</button>
<select id="cmbMethod">
<option value="RDP">Ramer-Douglas-Peucker</option>
<option value="Gaussian">Gaussian kernel</option>
</select>
<label for="txtPeriod">Interval: </label>
<input id="txtPeriod" type="text" style="width: 36px;" value="7" />
</div>
<div>
<button id="btnAddCalculated">Add calculated points</button>
<button id="btnClearCalculated">Clear calculated points</button>
</div>
<svg id="svg1" width="765" height="450" viewBox="0 0 510 300">
<g id="graph1" transform="translate(0,300) scale(1,-1)">
<rect width="500" height="300" stroke="black" fill="#eee"></rect>
<g id="grpRawData"></g>
<g id="grpCalculatedData"></g>
</g>
</svg>
Excuse the formatting -- it kept giving me "you have code unformatted" errors in this question. I have a code which consists of a canvas that prints an image. I then track the user's mouse coordinates, and if they match coordinates found in a text file, I want to prompt the user. This is the web-app to supplement a C++ OpenCV program that detects blobs and the coordinates where they exist. The text file is in the following format: label x y.
001 101 305
is the line for coordinate (101, 305) in blob 001. PHP is reading each line and exploding at spaces just fine.
The text file reads like so:
001 101 303
001 101 304
001 101 305
001 101 306
001 101 307
001 101 308
001 101 309
001 101 310
001 102 301
001 102 302
<script> // functions to make canvas and place PNG overlay image
<?php
$XYFile = fopen("cpp/textdump/coordinates/imagetesting.txt.txt","r") or die ("file reading error");
$coordinates = array(); // create coordinates array
while (!feof($XYFile)) { // while not at last line of file
$uncatXY = fgets($XYFile); // read one line of file
$splitXY = explode(" ", $uncatXY); // create new array element when a space is present
$label = $splitXY[0]; // declare blob label
$x = $splitXY[1]; // declare x
$y = $splitXY[2]; // declare y
array_push($coordinates, $label, $x, $y);
} // push into coordinates array
fclose($XYFile); // close file
?>
var js_array = <?php echo(json_encode($coordinates)); ?> // convert PHP array to javascript object notation format
// requires PHP 5.2 or higher, which I believe to be on the server. It's certainly on my localhost server.
console.log(js_array); // dev
$(document).ready(function() {
window.onload = function() {
var c = document.getElementById("solarCanvas");
var ctx = c.getContext("2d");
ctx.fillStyle = "#00FFFF"; // cyan fill
var img = document.getElementById("testimage");
img.src = 'cpp/images/PNG/imagetesting.png'; //"cpp/images/" + date + ".png";
ctx.drawImage(img, 0, 0);
c.addEventListener('click', function() { }, false);
// functions for color & coordinates on mouse movement/clicl
$('#solarCanvas').mousemove(function(e) {
var c = document.getElementById("solarCanvas");
var ctx = c.getContext("2d"); // temp declaration bug fix -- can't find root cause, so redec
var pos = findPos(this);
var x = e.pageX - pos.x;
var y = e.pageY - pos.y;
var coord = "x=" + x + ", y=" +y;
var p = ctx.getImageData(x, y, 1, 1).data;
var hex = ("#" + 000000 + rgbToHex(p[0], p[1], p[2]).slice(-6));
$('#status').html(coord + "<br>" + hex);
console.log(x + "," + y + "---" + coord + " at " + hex);
});
$('#solarCanvas').click(function(e) {
var c = document.getElementById("solarCanvas");
var ctx = c.getContext("2d");
var pos = findPos(this);
var xNum = e.pageX - pos.x;
var yNum = e.pageY - pos.y;
var xStr = xNum.toString();
var yStr = yNum.toString();
for(var i = 0; i < js_array.length; i++) {
if(js_array[i] === xStr) {
return i;
console.log(i);
if(i > -1) {
alert("yahoo!");
}
}
}
console.log(js_array);
console.log(xStr);
console.log(yStr);
});
}
});
// old architecutre
// mixed array_search ( mixed $needle, array $haystack [, bool $strict = false ] );
// searches haystack for needle
// pseudocode
//if ((e.pageX - pos.x) exists in col 2 && (e.pageY-pos.y) is in same row in col 3) {
</script>
I've been through many versions of this code and am posting what I have now. I am working strictly with the X coordinate at the moment. Once it works, I will add in Y.
[...]
I have considered the fact that the PHP explode & json_encode is saving strings, so I did a .toString() function but that did not seem to help at all. For example, my console log would be something like x/y/pos of x in array/pos of y in array
101
305
-1
-1
Here's a slight rewrite of your code.
What I did:
Changed your CSV parsing to split into rows of 3 columns, using an iterator model for simplicity
Changed your JS to match x-coords to row[1] and y-coords to row[2]
Not sure what the other code is intended to do on success... looks like you're still working on this.
The PHP code:
<?php
$file = file("cpp/textdump/coordinates/imagetesting.txt.txt"");
$coordinates = array();
for ($line in $file) {
array_push($coordinates, explode(" ", $line);
}
?>
var coords = <?= json_encode($coordinates) ?>;
The JS:
$('#solarCanvas').click(function(e) {
var c = document.getElementById("solarCanvas");
var ctx = c.getContext("2d");
var pos = findPos(this);
var xNum = e.pageX - pos.x;
var yNum = e.pageY - pos.y;
var xStr = xNum.toString();
var yStr = yNum.toString();
for(var i = 0; i < coords.length; i++) {
if(coords[i][1] === xStr && coords[i][2] === yStr) {
return i;
console.log('coord found', coords[i]);
}
}
console.log(coords);
console.log(xStr);
console.log(yStr);
});
****Clarification**: I'm not looking for the fastest code or optimization. I would like to understand why some code that seem to not be optimized or optimal run in fact in general consistently faster.
The short version
Why is this code:
var index = (Math.floor(y / scale) * img.width + Math.floor(x / scale)) * 4;
More performant than this one?
var index = Math.floor(ref_index) * 4;
The long version
This week, the author of Impact js published an article about some rendering issue:
http://www.phoboslab.org/log/2012/09/drawing-pixels-is-hard
In the article there was the source of a function to scale an image by accessing pixels in the canvas. I wanted to suggest some traditional ways to optimize this kind of code so that the scaling would be shorter at loading time. But after testing it my result was most of the time worst that the original function.
Guessing this was the JavaScript engine that was doing some smart optimization I tried to understand a bit more what was going on so I did a bunch of test. But my results are quite confusing and I would need some help to understand what's going on.
I have a test page here:
http://www.mx981.com/stuff/resize_bench/test.html
jsPerf: http://jsperf.com/local-variable-due-to-the-scope-lookup
To start the test, click the picture and the results will appear in the console.
There are three different versions:
The original code:
for( var y = 0; y < heightScaled; y++ ) {
for( var x = 0; x < widthScaled; x++ ) {
var index = (Math.floor(y / scale) * img.width + Math.floor(x / scale)) * 4;
var indexScaled = (y * widthScaled + x) * 4;
scaledPixels.data[ indexScaled ] = origPixels.data[ index ];
scaledPixels.data[ indexScaled+1 ] = origPixels.data[ index+1 ];
scaledPixels.data[ indexScaled+2 ] = origPixels.data[ index+2 ];
scaledPixels.data[ indexScaled+3 ] = origPixels.data[ index+3 ];
}
}
jsPerf: http://jsperf.com/so-accessing-local-variable-doesn-t-improve-performance
One of my attempt to optimize it:
var ref_index = 0;
var ref_indexScaled = 0
var ref_step = 1 / scale;
for( var y = 0; y < heightScaled; y++ ) {
for( var x = 0; x < widthScaled; x++ ) {
var index = Math.floor(ref_index) * 4;
scaledPixels.data[ ref_indexScaled++ ] = origPixels.data[ index ];
scaledPixels.data[ ref_indexScaled++ ] = origPixels.data[ index+1 ];
scaledPixels.data[ ref_indexScaled++ ] = origPixels.data[ index+2 ];
scaledPixels.data[ ref_indexScaled++ ] = origPixels.data[ index+3 ];
ref_index+= ref_step;
}
}
jsPerf: http://jsperf.com/so-accessing-local-variable-doesn-t-improve-performance
The same optimized code but with recalculating the index variable each time (Hybrid)
var ref_index = 0;
var ref_indexScaled = 0
var ref_step = 1 / scale;
for( var y = 0; y < heightScaled; y++ ) {
for( var x = 0; x < widthScaled; x++ ) {
var index = (Math.floor(y / scale) * img.width + Math.floor(x / scale)) * 4;
scaledPixels.data[ ref_indexScaled++ ] = origPixels.data[ index ];
scaledPixels.data[ ref_indexScaled++ ] = origPixels.data[ index+1 ];
scaledPixels.data[ ref_indexScaled++ ] = origPixels.data[ index+2 ];
scaledPixels.data[ ref_indexScaled++ ] = origPixels.data[ index+3 ];
ref_index+= ref_step;
}
}
jsPerf: http://jsperf.com/so-accessing-local-variable-doesn-t-improve-performance
The only difference in the two last one is the calculation of the 'index' variable.
And to my surprise the optimized version is slower in most browsers (except opera).
Results of personal testing (not the jsPerf tests):
Opera
Original: 8668ms
Optimized: 932ms
Hybrid: 8696ms
Chrome
Original: 139ms
Optimized: 145ms
Hybrid: 136ms
Safari
Original: 433ms
Optimized: 853ms
Hybrid: 451ms
Firefox
Original: 343ms
Optimized: 422ms
Hybrid: 350ms
After digging around, it seems an usual good practice is to access mainly local variable due to the scope lookup. Because The optimized version only call one local variable it should be faster that the Hybrid code which call multiple variable and object in addition to the various operation involved.
So why the "optimized" version is slower?
I thought that it might be because some JavaScript engine don't optimize the Optimized version because it is not hot enough but after using --trace-opt in chrome, it seems all version are properly compiled by V8.
At this point I am a bit clueless and wonder if somebody would know what is going on?
I did also some more test cases in this page:
http://www.mx981.com/stuff/resize_bench/index.html
As silly as it sounds, the Math.whatever() calls might be tricky to optimize and inline for the JS engines. Whenever possible, prefer an arithmetic operation (not a function call) to achieve the same result.
Adding the following 4th test to http://www.mx981.com/stuff/resize_bench/test.html
// Test 4
console.log('- p01 -');
start = new Date().getTime();
for (i=0; i<nbloop; i++) {
var index = 0;
var ref_indexScaled = 0
var ref_step=1/scale;
for( var y = 0; y < heightScaled; y++ ) {
for( var x = 0; x < widthScaled; x++ ) {
var z= index<<2;
scaledPixels.data[ ref_indexScaled++ ] = origPixels.data[ z++ ];
scaledPixels.data[ ref_indexScaled++ ] = origPixels.data[ z++ ];
scaledPixels.data[ ref_indexScaled++ ] = origPixels.data[ z++ ];
scaledPixels.data[ ref_indexScaled++ ] = origPixels.data[ z++ ];
index+= ref_step;
}
}
}
end = new Date().getTime();
console.log((end-start)+'ms');
Yields the following numbers in Opera Next:
Original - 2311ms
refactor - 112ms
hybrid - 2371ms
p01 - 112ms
Using some basic techniques you can highly optimize performance:
When running multiple loops in loops, use:
while (i--) {
/* some code here */
}
... where i is a value greater than 0.
Caching variables / localizing variables appropriately to minimize calculations. For larger calculations this means to place part of the calculation at the right layer of abstraction.
Re-using variables (re-initialization overhead can become a problem for large amounts of data processing). NOTE: This IS a bad programming design principle but a great performance principle!
Reduce property depth. Using object.property kills performance vs just a var containing "object_propertyvalue".
Using those principles you can achieve better performance. Now from a high level, looking at the article you derived this function from, it was flawed in a few ways. So to really optimize the full function instead of just the one line you stated:
function resize_Test5( img, scale ) {
// Takes an image and a scaling factor and returns the scaled image
// The original image is drawn into an offscreen canvas of the same size
// and copied, pixel by pixel into another offscreen canvas with the
// new size.
var widthScaled = img.width * scale;
var heightScaled = img.height * scale;
var orig = document.createElement('canvas');
orig.width = img.width;
orig.height = img.height;
var origCtx = orig.getContext('2d');
origCtx.drawImage(img, 0, 0);
var origPixels = origCtx.getImageData(0, 0, img.width, img.height);
var scaled = document.createElement('canvas');
scaled.width = widthScaled;
scaled.height = heightScaled;
var scaledCtx = scaled.getContext('2d');
var scaledPixels = scaledCtx.getImageData( 0, 0, widthScaled, heightScaled );
// optimization start
var old_list = origPixels.data;
var image_width = img.width;
var h = heightScaled;
var w = widthScaled;
var index_old;
var index_new;
var h_scale;
var new_list = [];
var pre_index_new;
while(h--){
h_scale = Math.floor(h / scale) * image_width;
pre_index_new = h * widthScaled;
while(w--){
index_old = (h_scale + Math.floor(w / scale)) * 4;
index_new = (pre_index_new + w) * 4;
new_list[ index_new ] = old_list[ index_old ];
new_list[ index_new + 1 ] = old_list[ index_old + 1 ];
new_list[ index_new + 2 ] = old_list[ index_old + 2 ];
new_list[ index_new + 3 ] = old_list[ index_old + 3 ];
}
}
scaledPixels.data = new_list;
// optimization stop
scaledCtx.putImageData( scaledPixels, 0, 0 );
return scaled;
}