D3. Create an Image Cloud with D3 - javascript
Im trying to create an Image Cloud component like the one in the image.
I've tried using a simple d3 alorithm with rects and force to center the images and to spread them with collisions. The problem is that it looks like this:
The images look too far apart one of each other, and even with lower collition radius the cloud doesn't use all avaliable space (fit smaller images between gaps). Is it there a way to make this component with the layouts given by d3? should I modify some other layout? I know I can implement an square collition force, but the results doesnt look as the one desired.
Any help is appreciated. Thank you very much.
EDIT: Hello again. I finally made it look somewhat similar to the desired result.
Sadly, now I have more problems. The first and most important one: the big images should be close one of each other and should be as centered as possible.
The second one: Im using React, and my data comes from calling the exported render function from outside the file (see first code snippet), the problem is that when I update the data, the image just dissapears and it doesn't adjust the layout again. How can i make it update it? A temporary hack that I made is to remove every image and rect from the DOM before rendering.
Again, thank you for your time.
Edit: I managed to get everything like I wanted. There is still room for improvement but this is functional for now. I managed to get an observer for the div and a somewhat good state management with React. Someday I will upload the react component to github
// specify svg width and height;
const width = 500, height = 500;
const listenTo = Math.min(width, height);
// create svg and g DOM elements;
let svg = d3.select('body')
.append('svg')
.attr('xmlns', 'http://www.w3.org/2000/svg')
.attr('width', listenTo)
.attr('height', listenTo)
.append('g')
// move 0,0 to the center
.attr('transform', `translate(${width >>1}, ${height>>1})`);
var images = [], maxImages = 100, maxWeight = 50, minWeight = 1, padding=3;
for(let i = 0; i< maxImages -1; i++){
const weight = (Math.random() *(maxWeight - minWeight)) + minWeight;
images.push({
url:`https://via.placeholder.com/100?text=${Math.ceil(weight)}`,
weight
})
}
// make one image with a weight 3 times bigger for visualization testing propouses
images.push({
url: `https://via.placeholder.com/100?text=${maxWeight * 3}`,
weight: maxWeight * 3,
fx: 0,
fy: 0
})
images.sort((a, b) => b.weight - a.weight);
// make it so the biggest images is equal to 10% of canvas, and thre smallest one 1%
const scl = ((100 / maxImages) / 100);
console.log(scl);
const maxImageSize = listenTo * 0.1;
const minImageSize = listenTo * scl;
// function to scale the images
const scaleSize = d3.scaleLinear().domain([minWeight, maxWeight*3]).range([minImageSize, maxImageSize]).clamp(true);
// append the rects
let vizImages = svg.selectAll('.image-cloud-image')
.data(images)
.enter()
.append('svg:image')
.attr('class', '.image-cloud-image')
.attr('height', d => scaleSize(d.weight))
.attr('width', d => scaleSize(d.weight))
.attr('id', d => d.url)
.attr('xlink:href', d => d.url);
vizImages.exit().remove();
// create the collection of forces
const simulation = d3.forceSimulation()
// set the nodes for the simulation to be our images
.nodes(images)
// set the function that will update the view on each 'tick'
.on('tick', ticked)
.force('center', d3.forceCenter())
.force('cramp', d3.forceManyBody().strength(listenTo / 100))
// collition force for rects
.force('collide', rectCollide().size(d=> {
const s = scaleSize(d.weight);
return [s + padding, s + padding];
}));
// update the position to new x and y
function ticked() {
vizImages.attr('x', d => d.x).attr('y', d=> d.y);
}
// Rect collition algorithm. i don't know exactly how it works
// https://bl.ocks.org/cmgiven/547658968d365bcc324f3e62e175709b
function rectCollide() {
var nodes, sizes, masses
var size = constant([0, 0])
var strength = 1
var iterations = 1
function force() {
var node, size, mass, xi, yi
var i = -1
while (++i < iterations) { iterate() }
function iterate() {
var j = -1
var tree = d3.quadtree(nodes, xCenter, yCenter).visitAfter(prepare)
while (++j < nodes.length) {
node = nodes[j]
size = sizes[j]
mass = masses[j]
xi = xCenter(node)
yi = yCenter(node)
tree.visit(apply)
}
}
function apply(quad, x0, y0, x1, y1) {
var data = quad.data
var xSize = (size[0] + quad.size[0]) / 2
var ySize = (size[1] + quad.size[1]) / 2
if (data) {
if (data.index <= node.index) { return }
var x = xi - xCenter(data)
var y = yi - yCenter(data)
var xd = Math.abs(x) - xSize
var yd = Math.abs(y) - ySize
if (xd < 0 && yd < 0) {
var l = Math.sqrt(x * x + y * y)
var m = masses[data.index] / (mass + masses[data.index])
if (Math.abs(xd) < Math.abs(yd)) {
node.vx -= (x *= xd / l * strength) * m
data.vx += x * (1 - m)
} else {
node.vy -= (y *= yd / l * strength) * m
data.vy += y * (1 - m)
}
}
}
return x0 > xi + xSize || y0 > yi + ySize ||
x1 < xi - xSize || y1 < yi - ySize
}
function prepare(quad) {
if (quad.data) {
quad.size = sizes[quad.data.index]
} else {
quad.size = [0, 0]
var i = -1
while (++i < 4) {
if (quad[i] && quad[i].size) {
quad.size[0] = Math.max(quad.size[0], quad[i].size[0])
quad.size[1] = Math.max(quad.size[1], quad[i].size[1])
}
}
}
}
}
function xCenter(d) { return d.x + d.vx + sizes[d.index][0] / 2 }
function yCenter(d) { return d.y + d.vy + sizes[d.index][1] / 2 }
force.initialize = function (_) {
sizes = (nodes = _).map(size)
masses = sizes.map(function (d) { return d[0] * d[1] })
}
force.size = function (_) {
return (arguments.length
? (size = typeof _ === 'function' ? _ : constant(_), force)
: size)
}
force.strength = function (_) {
return (arguments.length ? (strength = +_, force) : strength)
}
force.iterations = function (_) {
return (arguments.length ? (iterations = +_, force) : iterations)
}
return force
}
function constant(_) {
return function () { return _ }
}
Here's a fiddle for future people
Related
The maximum volume of a box
Trying to write a simple web app to solve the following common calculus problem in JavaScript. Suppose you wanted to make an open-topped box out of a flat piece of cardboard that is L long by W wide by cutting the same size square (h × h) out of each corner and then folding the flaps to form the box, as illustrated below: You want to find out how big to make the cut-out squares in order to maximize the volume of the box. Ideally I want to avoid using any calculus library to solve this. My initial naive solution: // V = l * w * h function getBoxVolume(l, w, h) { return (l - 2*h)*(w - 2*h)*h; } function findMaxVol(l, w) { const STEP_SIZE = 0.0001; let ideal_h = 0; let max_vol = 0; for (h = 0; h <= Math.min(l, w) / 2; h = h + STEP_SIZE) { const newVol = getBoxVolume(l, w, h); if (max_vol <= newVol) { ideal_h = h; max_vol = newVol; } else { break; } } return { ideal_h, max_vol } } const WIDTH_1 = 20; const WIDTH_2 = 30; console.log(findMaxVol(WIDTH_1, WIDTH_2)) // { // ideal_h: 3.9237000000038558, // max_vol: 1056.3058953402121 // } The problem with this naive solution is that it only gives an estimate because you have to provide STEP_SIZE and it heavily limits the size of the problem this can solve.
You have an objective function: getBoxVolume(). Your goal is to maximize the value of this function. Currently, you're maximizing it using something equivalent to sampling: you're checking every STEP_SIZE, to see whether you get a better result. You've identified the main problem: there's no guarantee the edge of the STEP_SIZE interval falls anywhere near the max value. Observe something about your objective function: it's convex. I.e., it starts by going up (when h = 0, volume is zero, then it grows as h does), it reaches a maximum, then it goes down, eventually reaching zero (when h = min(l,w)/2). This means that there's guaranteed to be one maximum value, and you just need to find it. This makes this problem a great case for binary search, because given the nature of the function, you can sample two points on the function and know which direction the maximum lies relative to those two points. You can use this, with three points at a time (left, right, middle), to figure out whether the max is between left and middle, or middle and right. Once these values get close enough together (they're within some fixed amount e of each other), you can return the value of the function there. You can even prove that the value you return is within some value e' of the maximum possible value. Here's pseudocode: max(double lowerEnd, upperEnd) { double midPoint = (upperEnd + lowerEnd) / 2 double midValue = getBoxVolume(l, w, midpoint) double slope = (getBoxVolume(l, w, midpoint + epsilon) - midValue) / epsilon if (Math.abs(slope) < epsilon2) { // or, if you choose, if (upperEnd - lowerEnd < epsilon3) return midpoint } if (slope < 0) { // we're on the downslope return max(lowerEnd, midPoint) } else { // we're on the up-slope return max(midpoint, upperEnd) } }
After realising that the derivative of the volume function is a second degree polynomial you can apply a quadratic formula to solve for x. Using calculus, the vertex point, being a maximum or minimum of the function, can be obtained by finding the roots of the derivative // V = l * w * h function getBoxVolume(l, w, h) { return (l - 2*h)*(w - 2*h)*h; } // ax^2 + bx + c = 0 function solveQuad(a, b, c) { var x1 = (-1 * b + Math.sqrt(Math.pow(b, 2) - (4 * a * c))) / (2 * a); var x2 = (-1 * b - Math.sqrt(Math.pow(b, 2) - (4 * a * c))) / (2 * a); return { x1, x2 }; } function findMaxVol(l, w) { // V'(h) = 12h^2-4(l+w)h+l*w - second degree polynomial // solve to get the critical numbers const result = solveQuad(12, -4*(l + w), l*w) const vol1 = getBoxVolume(l, w, result.x1); const vol2 = getBoxVolume(l, w, result.x2); let ideal_h = 0; let max_vol = 0; // check for max if (vol1 > vol2) { ideal_h = result.x1; max_vol = vol1; } else { ideal_h = result.x2; max_vol = vol2; } return { ideal_h, max_vol } } const WIDTH_1 = 20; const WIDTH_2 = 30; console.log(findMaxVol(WIDTH_1, WIDTH_2)) // { // ideal_h: 3.9237478148923493, // max_vol: 1056.30589546119 // }
Are complex functions plots possible in jsx graph?
Suppose one attempts to plot the complex valued function $f:\mathhbb{C} \to \mathhbb{C}$ as $f(z) =z$ in jsx graph. It may not be complicated as it appears. What one needs is two connected planes. The point (x, y) in domain planr gets mapped to the point (x, y) in codomain plane. As one drags point in domain plane, corresponding changes takes place in the point in co domain plane. So the only question is how to connect two planes. It is matter of 2 dimensions only. If something similar to the following can be added to jsx graph, it would be great addition to jsx graph. Many properties of complex valued function can then be studied. Here is the link. http://www.jimrolf.com/java/complexTool/bookComplexTool.html
Two boards board1, board2 can be connected with board1.addChild(board2). This means, every update in board1 triggers an update in board2. Here is a basic example, see https://jsfiddle.net/zfbrsdwh/ : const board1 = JXG.JSXGraph.initBoard('jxgbox1', { boundingbox: [-5, 5, 5, -5], axis:true }); var p = board1.create('point', [1,2], {name:'Drag me'}); const board2 = JXG.JSXGraph.initBoard('jxgbox2', { boundingbox: [-5, 5, 5, -5], axis:true }); var q = board2.create('point', [function() { return [-p.Y(), p.X()]; }], {name:'image'}); board1.addChild(board2); Update in reply to the first comment: Visualizing conformal maps in the complex plane can be done by applying the map to a quadrangle. It is necessary to define the edges of the quadrangle by a curve: var p0 = board1.create('point', [2, -2]); var p1 = board1.create('point', [2, 2]); var p2 = board1.create('point', [-2, 2]); var p3 = board1.create('point', [-2, -2]); // Draw the quadrangle through p0, p1, p2, p3 as curve // defined by [fx, fy] var fx = function(x) { if (x < 0 || x > 4) { return NaN; } if (x < 1) { return (p1.X() - p0.X()) * x + p0.X(); } else if (x < 2) { return (p2.X() - p1.X()) * (x - 1) + p1.X(); } else if (x < 3) { return (p3.X() - p2.X()) * (x - 2) + p2.X(); } else if (x < 4) { return (p0.X() - p3.X()) * (x - 3) + p3.X(); } }; var fy = function(x) { if (x < 0 || x > 4) { return NaN; } if (x < 1) { return (p1.Y() - p0.Y()) * x + p0.Y(); } else if (x < 2) { return (p2.Y() - p1.Y()) * (x - 1) + p1.Y(); } else if (x < 3) { return (p3.Y() - p2.Y()) * (x - 2) + p2.Y(); } else if (x < 4) { return (p0.Y() - p3.Y()) * (x - 3) + p3.Y(); } }; var graph1 = board1.create('curve', [fx, fy, 0, 4]); Then it should be easy to define a conformal map and plot the composition of the two maps in the second board: // Conformal complex map z -> 1/z var map = function(x, y) { var s = x*x+y*y; return [x / s, -y/s]; }; // Draw the image of the quadrangle under the map f2x = function(x) { return map(fx(x), fy(x))[0]; }; f2y = function(x) { return map(fx(x), fy(x))[1]; }; var graph2 = board2.create('curve', [f2x, f2y, 0, 4]); The full mathlet is at https://jsfiddle.net/Lmy60f4g/2/
Adding points to Voronoi Diagram in d3
I am using d3 to create a diagram to try and speed up a nearest nabour search within a function that plots points on a plane. Is there a way to add points directly to the diagram so I can add the points within a while loop instead of re-drawing the entire voronoi? var svg = d3.select("svg") var distance = function(pa, pb) { var x = pa[0] - pb[0], y = pa[1] - pb[1] return Math.sqrt((x * x) + (y * y)) } var scatterCircle = function(point, radius, quantity, proximity, margin) { var x1 = point[0] - radius, y1 = point[1] - radius, inner = radius * margin, array = [ [500, 500] ] //should be declaring diagram here and addings points below// while (array.length < quantity) { //constructing new diagram each loop to test function, needs add to diagram function// var newpoly = d3.voronoi()(array), x = x1 + (radius * 2 * Math.random()), y = y1 + (radius * 2 * Math.random()), ii = newpoly.find(x, y).index var d = distance(array[ii], [x, y]), e = distance([x, y], point) if (e < inner) { if (d > proximity) { array.push([x, y]) } } } return array } var test = scatterCircle([500, 500], 500, 1500, 10, 0.9) var o = 0 while (o < test.length) { svg.append("circle") .attr("cx", test[o][0]) .attr("cy", test[o][1]) .attr("r", 1) o++ } <script src="https://d3js.org/d3.v4.js"></script> <svg width="1000" height="1000">
I am no expert in d3.js but I will share what I found out. The implemented algorithm for Voronoi diagrams is Fortune's algorithm. This is the classical algorithm to compute a Voronoi diagram. Inserting a new point is neither part of this algorithm nor of the function set documented for d3.js. But you are correct, inserting one new site does not require to redraw the whole diagram in theory. You use the Voronoi diagram for NNS (nearest neighbour search). You could also use a 2d-tree to accomplish NNS. There insertion and removal is easier. A quick search revealed two implementations in javascript: kd-tree-javascript and kd-tree-js.
d3.js scaling and translating a force layout to the center of a viewport
I have a d3.js force layout with some tweaks to the force calculations. As I'm working with large datasets, sometimes the graph is partly or entirely outside of the viewport. I'd like to add a command to rescale and center the graph to be inside the viewport, but having some trouble with that. what works: I have a canvas and a viewport onto it: this.svg_canvas = d3.select("#" + this.container_id) .append("svg") .attr("width", this.width) .attr("height", this.height) .call(this.zoom_behavior.bind(this)) ; this.viewport = this.svg_canvas.append("g") .attr("id", "viewport") ; I have a zoom behavior that scales and translates the viewport: this.zoom_behavior = d3.behavior.zoom() .scaleExtent([GraphView.MIN_ZOOM, GraphView.MAX_ZOOM]) .on('zoom', this._handleZoom.bind(this)) ; GraphView.prototype._handleZoom = function() { var translate = d3.event.translate; var scale = d3.event.scale; this.viewport.attr("transform", "translate(" + translate + ") " + "scale(" + scale + ")"); }; All of that works as it should. what doesn't work I added a "recenter and scale" method that is supposed to perform the scaling and translation to bring the graph onto the viewport. The way it is supposed to work is that it first finds the extent of the graph (via my boundingBox() method), then call zoom.behavior with the appropriate scaling and translation arguments: GraphView.prototype.recenterAndScale = function(nodes) { var bounding_box = this._boundingBox(nodes || this.force.nodes()); var viewport = this.zoom_behavior.size(); // viewport [width, height] var tx = viewport[0]/2 - bounding_box.x0; var ty = viewport[1]/2 - bounding_box.y0; var scale = Math.min(viewport[0]/bounding_box.dx, viewport[1]/bounding_box.dy); this.zoom_behavior.translate([tx, ty]) .scale(scale) .event(this.svg_canvas) ; }; This doesn't work. It (usually) locates the graph the edge of my viewport. Perhaps I'm using the wrong reference for something. Is there an online example of how to do this "properly" (using the d3 idioms)? For completeness, here's my definition of boundingBox() -- it returns the geometric center and extent of the nodes in the graph. As far as I can tell, this is working properly: // Return {dx:, dy:, x0:, y0:} for the given nodes. If no nodes // are given, return {dx: 0, dy: 0, x0: 0, y0: 0} GraphView.prototype._boundingBox = function(nodes) { if (nodes.length === 0) { return {dx: 0, dy: 0, x0: 0, y0: 0}; } else { var min_x = Number.MAX_VALUE; var min_y = Number.MAX_VALUE; var max_x = -Number.MAX_VALUE; var max_y = -Number.MAX_VALUE; nodes.forEach(function(node) { if (node.x < min_x) min_x = node.x; if (node.x > max_x) max_x = node.x; if (node.y < min_y) min_y = node.y; if (node.y > max_y) max_y = node.y; }); return {dx: max_x - min_x, dy: max_y - min_y, x0: (max_x + min_x) / 2.0, y0: (max_y + min_y) / 2.0 }; } }
This was actually pretty easy: I needed to apply the effects of scaling when computing the translation. The corrected recenterAndScale() method is: GraphView.prototype.recenterAndScale = function(nodes) { var bbox = this._boundingBox(nodes || this.force.nodes()); var viewport = this.zoom_behavior.size(); // => [width, height] var scale = Math.min(viewport[0]/bbox.dx, viewport[1]/bbox.dy); var tx = viewport[0]/2 - bbox.x0 * scale; // <<< account for scale var ty = viewport[1]/2 - bbox.y0 * scale; // <<< account for scale this.zoom_behavior.translate([tx, ty]) .scale(scale) .event(this.svg_canvas) ; };
Why is Firefox 30 times slower than Chrome, when calculating Perlin noise?
I have written a map generator in javascript, using classical perlin noise scripts I have found in various places, to get the functionality I want. I have been working in chrome, and have not experienced any problems with the map. However, when I tested it in firefox, it was incredibly slow - almost hanging my system. It fared better in the nightly build, but still 30 times slower than Chrome. You can find a test page of it here: http://jsfiddle.net/7Gq3s/ Here is the html code: <!DOCTYPE html> <html> <head> <title>PerlinMapTest</title> </head> <body> <canvas id="map" width="100" height="100" style="border: 1px solid red">My Canvas</canvas> <script src="//code.jquery.com/jquery-2.0.0.min.js"></script> <script> $(document).ready(function(){ //Log time in two ways var startTime = new Date().getTime(); console.time("Map generated in: "); var canvas = $("#map")[0]; var ctx = canvas.getContext("2d"); var id = ctx.createImageData(canvas.width, canvas.height); var noiseMap = new PerlinNoise(500); var startx = 0; var starty = 0; var value = 0; for(var i = startx; i < canvas.width; i++){ for(var j = starty; j < canvas.height; j++){ value = noiseMap.noise(i,j, 0, 42); value = linear(value,-1,1,0,255); setPixel(id, i, j, 0,0,0,value); } } ctx.putImageData(id,0,0); var endTime = new Date().getTime(); console.timeEnd("Map generated in: "); alert("Map generated in: " + (endTime - startTime) + "milliseconds"); }); function setPixel(imageData, x, y, r, g, b, a) { index = (x + y * imageData.width) * 4; imageData.data[index+0] = r; imageData.data[index+1] = g; imageData.data[index+2] = b; imageData.data[index+3] = a; } //This is a port of Ken Perlin's "Improved Noise" //http://mrl.nyu.edu/~perlin/noise/ //Originally from http://therandomuniverse.blogspot.com/2007/01/perlin-noise-your-new-best-friend.html //but the site appears to be down, so here is a mirror of it //Converted from php to javascript by Christian Moe //Patched the errors with code from here: http://asserttrue.blogspot.fi/2011/12/perlin-noise-in-javascript_31.html var PerlinNoise = function(seed) { this._default_size = 64; this.seed = seed; //Initialize the permutation array. this.p = new Array(512); this.permutation = [ 151,160,137,91,90,15, 131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23, 190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33, 88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166, 77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244, 102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196, 135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123, 5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42, 223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9, 129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228, 251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107, 49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254, 138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180 ]; for (var i=0; i < 256 ; i++) { this.p[256+i] = this.p[i] = this.permutation[i]; } }; PerlinNoise.prototype.noise = function(x,y,z,size) { if (size == undefined) { size = this._default_size; } //Set the initial value and initial size var value = 0.0; var initialSize = size; //Add finer and finer hues of smoothed noise together while(size >= 1) { value += this.smoothNoise(x / size, y / size, z / size) * size; size /= 2.0; } //Return the result over the initial size return value / initialSize; }; //This function determines what cube the point passed resides in //and determines its value. PerlinNoise.prototype.smoothNoise = function(x, y, z){ //Offset each coordinate by the seed value x += this.seed; y += this.seed; z += this.seed; var orig_x = x; var orig_y = y; var orig_z = z; var X = Math.floor(x) & 255, // FIND UNIT CUBE THAT Y = Math.floor(y) & 255, // CONTAINS POINT. Z = Math.floor(z) & 255; x -= Math.floor(x); // FIND RELATIVE X,Y,Z y -= Math.floor(y); // OF POINT IN CUBE. z -= Math.floor(z); var u = this.fade(x), // COMPUTE FADE CURVES v = this.fade(y), // FOR EACH OF X,Y,Z. w = this.fade(z); var A = this.p[X ]+Y, AA = this.p[A]+Z, AB = this.p[A+1]+Z, // HASH COORDINATES OF B = this.p[X+1]+Y, BA = this.p[B]+Z, BB = this.p[B+1]+Z; // THE 8 CUBE CORNERS, return this.lerp(w, this.lerp(v, this.lerp(u, this.grad(this.p[AA ], x , y , z ), // AND ADD this.grad(this.p[BA ], x-1, y , z )), // BLENDED this.lerp(u, this.grad(this.p[AB ], x , y-1, z ), // RESULTS this.grad(this.p[BB ], x-1, y-1, z ))),// FROM 8 this.lerp(v, this.lerp(u, this.grad(this.p[AA+1], x , y , z-1 ), // CORNERS this.grad(this.p[BA+1], x-1, y , z-1 )), // OF CUBE this.lerp(u, this.grad(this.p[AB+1], x , y-1, z-1 ), this.grad(this.p[BB+1], x-1, y-1, z-1 )))); }; PerlinNoise.prototype.fade = function(t) { return t * t * t * ( ( t * ( (t * 6) - 15) ) + 10); }; PerlinNoise.prototype.lerp = function(t, a, b) { //Make a weighted interpolaton between points return a + t * (b - a); }; PerlinNoise.prototype.grad = function(hash, x, y, z) { h = hash & 15; // CONVERT LO 4 BITS OF HASH CODE u = h<8 ? x : y; // INTO 12 GRADIENT DIRECTIONS. v = h<4 ? y : (h==12||h==14 ? x : z); return ((h&1) == 0 ? u : -u) + ((h&2) == 0 ? v : -v); }; PerlinNoise.prototype.scale = function(n) { return (1 + n)/2; }; function linear(int, s1, s2, t1, t2) { t = [t1, t2]; s = [s1, s2]; rangeS = s1 - s2; rangeT = t1 - t2; if((s1 < s2 && t1 > t2) || (s1>s2 && t1<t2)) { interpolated = ((int - s1) / rangeS*rangeT) + t1; } else { interpolated = ((int - s1) / rangeS)*rangeT + t1; } if(interpolated > Math.max.apply(Math, t)) { interpolated = Math.max.apply(Math, t); } if(interpolated < Math.min.apply(Math, t)) { interpolated = Math.min.apply(Math, t); } return interpolated; } </script> </body> </html> I get 33 ms on Chrome, and 1051ms on Firefox 24 Nightly The results are inconsistent though. Sometimes the Nightly results is as fast as chrome... Do you know why there is so much variation in this particular instance? I don't know enough about the theory of perlin noise to try optimizing the code, so don't know what to do.
I have found the culprit. The slowdown occurs when I have Firebug enabled. That extension must weigh it down.