I made this simple Mandelbrot Set generator in Javascript last night, but it outputs a really strange structure. I think it looks similar to the mandelbrot set, yet oddly deformed. I have no idea why it distorts like this, and I've been trying to find out all day. Does anyone know what causes this or how to fix this?
c = document.getElementById("canvas");
ctx = c.getContext("2d");
c.width = 4000;
c.height = 4000;
declareVariables();
calculateShape();
drawShape();
function declareVariables() {
Re = -2;
Im = -2;
input = [Re,Im];
precision = prompt("input precision (higher is better)");
precision = 1/(precision - precision%4);
segmentAmt = 4/precision;
segmentSize = c.width/segmentAmt;
iterate = prompt("input test amount (higher is better)");
set = [];
for (i=0; i<segmentAmt; i++) {
set[i] = [];
}
numberGrid = [];
for (i=0; i<segmentAmt; i++) {
numberGrid[i] = [];
for (j=0; j<segmentAmt; j++) {
}
}
}
function calculateShape() {
for (i=0; i<segmentAmt; i++) {
input[1] = -2;
input[0] += precision;
for (j=0; j<segmentAmt; j++) {
input[1] += precision;
set[i][j] = 0;
z = [0,0];
for (k=1; k<=iterate; k++) {
store = z;
z[0] = store[0]**2 - store[1]**2 + input[0];
z[1] = 2 * store[0] * store[1] + input[1];
if (z[0]**2 + z[1]**2 > 4) {
set[i][j] = k;
k = iterate+1;
}
}
}
}
}
function drawShape() {
ctx.fillStyle = "white";
ctx.fillRect(0,0,c.width,c.height);
for (i=0; i<segmentAmt; i++) {
for (j=0; j<segmentAmt; j++) {
if (set[i][j] == 0) {
ctx.fillStyle = "black";
} else if (set[i][j] >= 1) {
ctx.fillStyle = 'hsl(' + (25*(set[i][j]-1))**0.75 + ', 100%, 50%)';
}
convertCoords(i,j);
ctx.fillRect(xCoord,yCoord,segmentSize,segmentSize);
}
}
}
function convertCoords(var1,var2) {
xCoord = var1 * segmentSize;
yCoord = var2 * segmentSize;
}
Output image:
The error appears to be on this line in calculateShape():
store = z;
It seems you want store to be a copy of z, but this just ends up with store and z referring to the same array. The next line calculates z[0], but as store and z refer to the same array, store[0] has the new value of z[0] rather than the previous. Hence the calculation of z[1] in the line after that is incorrect.
Replace the above line with either
store = [z[0], z[1]];
or
store = z.slice();
Both of these lines ensure that store refers to a different array to z, so when you recalculate z[0], store[0] is unaffected.
Related
I am trying to add text on an image using the <canvas> element. First the image is drawn and on the image the text is drawn. So far so good.
But where I am facing a problem is that if the text is too long, it gets cut off in the start and end by the canvas. I don't plan to resize the canvas, but I was wondering how to wrap the long text into multiple lines so that all of it gets displayed. Can anyone point me at the right direction?
Updated version of #mizar's answer, with one severe and one minor bug fixed.
function getLines(ctx, text, maxWidth) {
var words = text.split(" ");
var lines = [];
var currentLine = words[0];
for (var i = 1; i < words.length; i++) {
var word = words[i];
var width = ctx.measureText(currentLine + " " + word).width;
if (width < maxWidth) {
currentLine += " " + word;
} else {
lines.push(currentLine);
currentLine = word;
}
}
lines.push(currentLine);
return lines;
}
We've been using this code for some time, but today we were trying to figure out why some text wasn't drawing, and we found a bug!
It turns out that if you give a single word (without any spaces) to the getLines() function, it will return an empty array, rather than an array with a single line.
While we were investigating that, we found another (much more subtle) bug, where lines can end up slightly longer than they should be, since the original code didn't account for spaces when measuring the length of a line.
Our updated version, which works for everything we've thrown at it, is above. Let me know if you find any bugs!
A possible method (not completely tested, but as for now it worked perfectly)
/**
* Divide an entire phrase in an array of phrases, all with the max pixel length given.
* The words are initially separated by the space char.
* #param phrase
* #param length
* #return
*/
function getLines(ctx,phrase,maxPxLength,textStyle) {
var wa=phrase.split(" "),
phraseArray=[],
lastPhrase=wa[0],
measure=0,
splitChar=" ";
if (wa.length <= 1) {
return wa
}
ctx.font = textStyle;
for (var i=1;i<wa.length;i++) {
var w=wa[i];
measure=ctx.measureText(lastPhrase+splitChar+w).width;
if (measure<maxPxLength) {
lastPhrase+=(splitChar+w);
} else {
phraseArray.push(lastPhrase);
lastPhrase=w;
}
if (i===wa.length-1) {
phraseArray.push(lastPhrase);
break;
}
}
return phraseArray;
}
Here was my spin on it... I read #mizar's answer and made some alterations to it... and with a little assistance I Was able to get this.
code removed, see fiddle.
Here is example usage. http://jsfiddle.net/9PvMU/1/ - this script can also be seen here and ended up being what I used in the end... this function assumes ctx is available in the parent scope... if not you can always pass it in.
edit
the post was old and had my version of the function that I was still tinkering with. This version seems to have met my needs thus far and I hope it can help anyone else.
edit
It was brought to my attention there was a small bug in this code. It took me some time to get around to fixing it but here it is updated. I have tested it myself and it seems to work as expected now.
function fragmentText(text, maxWidth) {
var words = text.split(' '),
lines = [],
line = "";
if (ctx.measureText(text).width < maxWidth) {
return [text];
}
while (words.length > 0) {
var split = false;
while (ctx.measureText(words[0]).width >= maxWidth) {
var tmp = words[0];
words[0] = tmp.slice(0, -1);
if (!split) {
split = true;
words.splice(1, 0, tmp.slice(-1));
} else {
words[1] = tmp.slice(-1) + words[1];
}
}
if (ctx.measureText(line + words[0]).width < maxWidth) {
line += words.shift() + " ";
} else {
lines.push(line);
line = "";
}
if (words.length === 0) {
lines.push(line);
}
}
return lines;
}
context.measureText(text).width is what you're looking for...
Try this script to wrap the text on a canvas.
<script>
function wrapText(ctx, text, x, y, maxWidth, lineHeight) {
var words = text.split(' ');
var line = '';
for(var n = 0; n < words.length; n++) {
var testLine = line + words[n] + ' ';
var metrics = ctx.measureText(testLine);
var testWidth = metrics.width;
if (testWidth > maxWidth && n > 0) {
ctx.fillText(line, x, y);
line = words[n] + ' ';
y += lineHeight;
}
else {
line = testLine;
}
}
ctx.fillText(line, x, y);
}
var canvas = document.getElementById('Canvas01');
var ctx = canvas.getContext('2d');
var maxWidth = 400;
var lineHeight = 24;
var x = (canvas.width - maxWidth) / 2;
var y = 70;
var text = 'HTML is the language for describing the structure of Web pages. HTML stands for HyperText Markup Language. Web pages consist of markup tags and plain text. HTML is written in the form of HTML elements consisting of tags enclosed in angle brackets (like <html>). HTML tags most commonly come in pairs like <h1> and </h1>, although some tags represent empty elements and so are unpaired, for example <img>..';
ctx.font = '15pt Calibri';
ctx.fillStyle = '#555555';
wrapText(ctx, text, x, y, maxWidth, lineHeight);
</script>
</body>
See demo here http://codetutorial.com/examples-canvas/canvas-examples-text-wrap.
From the script here: http://www.html5canvastutorials.com/tutorials/html5-canvas-wrap-text-tutorial/
I've extended to include paragraph support. Use \n for new line.
function wrapText(context, text, x, y, line_width, line_height)
{
var line = '';
var paragraphs = text.split('\n');
for (var i = 0; i < paragraphs.length; i++)
{
var words = paragraphs[i].split(' ');
for (var n = 0; n < words.length; n++)
{
var testLine = line + words[n] + ' ';
var metrics = context.measureText(testLine);
var testWidth = metrics.width;
if (testWidth > line_width && n > 0)
{
context.fillText(line, x, y);
line = words[n] + ' ';
y += line_height;
}
else
{
line = testLine;
}
}
context.fillText(line, x, y);
y += line_height;
line = '';
}
}
Text can be formatted like so:
var text =
[
"Paragraph 1.",
"\n\n",
"Paragraph 2."
].join("");
Use:
wrapText(context, text, x, y, line_width, line_height);
in place of
context.fillText(text, x, y);
I am posting my own version used here since answers here weren't sufficient for me. The first word needed to be measured in my case, to be able to deny too long words from small canvas areas. And I needed support for 'break+space, 'space+break' or double-break/paragraph-break combos.
wrapLines: function(ctx, text, maxWidth) {
var lines = [],
words = text.replace(/\n\n/g,' ` ').replace(/(\n\s|\s\n)/g,'\r')
.replace(/\s\s/g,' ').replace('`',' ').replace(/(\r|\n)/g,' '+' ').split(' '),
space = ctx.measureText(' ').width,
width = 0,
line = '',
word = '',
len = words.length,
w = 0,
i;
for (i = 0; i < len; i++) {
word = words[i];
w = word ? ctx.measureText(word).width : 0;
if (w) {
width = width + space + w;
}
if (w > maxWidth) {
return [];
} else if (w && width < maxWidth) {
line += (i ? ' ' : '') + word;
} else {
!i || lines.push(line !== '' ? line.trim() : '');
line = word;
width = w;
}
}
if (len !== i || line !== '') {
lines.push(line);
}
return lines;
}
It supports any variants of lines breaks, or paragraph breaks, removes double spaces, as well as leading or trailing paragraph breaks. It returns either an empty array if the text doesn't fit. Or an array of lines ready to draw.
look at https://developer.mozilla.org/en/Drawing_text_using_a_canvas#measureText%28%29
If you can see the selected text, and see its wider than your canvas, you can remove words, until the text is short enough. With the removed words, you can start at the second line and do the same.
Of course, this will not be very efficient, so you can improve it by not removing one word, but multiple words if you see the text is much wider than the canvas width.
I did not research, but maybe their are even javascript libraries that do this for you
I modified it using the code from here http://miteshmaheta.blogspot.sg/2012/07/html5-wrap-text-in-canvas.html
http://jsfiddle.net/wizztjh/kDy2U/41/
This should bring the lines correctly from the textbox:-
function fragmentText(text, maxWidth) {
var lines = text.split("\n");
var fittingLines = [];
for (var i = 0; i < lines.length; i++) {
if (canvasContext.measureText(lines[i]).width <= maxWidth) {
fittingLines.push(lines[i]);
}
else {
var tmp = lines[i];
while (canvasContext.measureText(tmp).width > maxWidth) {
tmp = tmp.slice(0, tmp.length - 1);
}
if (tmp.length >= 1) {
var regex = new RegExp(".{1," + tmp.length + "}", "g");
var thisLineSplitted = lines[i].match(regex);
for (var j = 0; j < thisLineSplitted.length; j++) {
fittingLines.push(thisLineSplitted[j]);
}
}
}
}
return fittingLines;
And then get draw the fetched lines on the canvas :-
var lines = fragmentText(textBoxText, (rect.w - 10)); //rect.w = canvas width, rect.h = canvas height
for (var showLines = 0; showLines < lines.length; showLines++) { // do not show lines that go beyond the height
if ((showLines * resultFont.height) >= (rect.h - 10)) { // of the canvas
break;
}
}
for (var i = 1; i <= showLines; i++) {
canvasContext.fillText(lines[i-1], rect.clientX +5 , rect.clientY + 10 + (i * (resultFont.height))); // resultfont = get the font height using some sort of calculation
}
This is a typescript version of #JBelfort's answer.
(By the way, thanks for this brilliant code)
As he mentioned in his answer this code can simulate html element such as textarea,and also the CSS property
word-break: break-all
I added canvas location parameters (x, y and lineHeight)
function wrapText(
ctx: CanvasRenderingContext2D,
text: string,
maxWidth: number,
x: number,
y: number,
lineHeight: number
) {
const xOffset = x;
let yOffset = y;
const lines = text.split('\n');
const fittingLines: [string, number, number][] = [];
for (let i = 0; i < lines.length; i++) {
if (ctx.measureText(lines[i]).width <= maxWidth) {
fittingLines.push([lines[i], xOffset, yOffset]);
yOffset += lineHeight;
} else {
let tmp = lines[i];
while (ctx.measureText(tmp).width > maxWidth) {
tmp = tmp.slice(0, tmp.length - 1);
}
if (tmp.length >= 1) {
const regex = new RegExp(`.{1,${tmp.length}}`, 'g');
const thisLineSplitted = lines[i].match(regex);
for (let j = 0; j < thisLineSplitted!.length; j++) {
fittingLines.push([thisLineSplitted![j], xOffset, yOffset]);
yOffset += lineHeight;
}
}
}
}
return fittingLines;
}
and you can just use this like
const wrappedText = wrapText(ctx, dialog, 200, 100, 200, 50);
wrappedText.forEach(function (text) {
ctx.fillText(...text);
});
}
I'm trying to make a program that takes a string, converts it to base64, and then to binary. It then takes the binary and changes the pixels a black pixel for 0 and a white pixel for 1.
I've gotten the pixel array to change to what I want, but it's not actually changing when I call updatePixels().
My goal is to then take the canvas and export it as an image.
My sketch:
let hw;
let input, button;
let binaryOut;
function setup() {
createCanvas(140,140);
input=createInput();
pixelDensity(1);
button = createButton("get image");
button.mousePressed(txtTo64ToBin)
loadPixels();
}
function txtTo64ToBin(){
str = input.value();
str = btoa(str);
let output = '';
str = str.split("")
for(let i=0;i<str.length;i++){
let base = str[i].charCodeAt(0).toString(2)
while(base.length < 8){
base = "0"+base;
}
output += base;
}
binaryOut = output;
console.log(binaryOut)
updateImage(binaryOut.split(''))
}
function updateImage(binArr){
hw = factors(binArr.length);
hw = hw[hw.length-1];
console.log(hw);
resizeCanvas(...hw,false)
pixels = []
for(let i=0; i<binArr.length; i++){
pixels[i*4] = map(binArr[i],0,1,0,255);
pixels[i*4+1] = map(binArr[i],0,1,0,255);
pixels[i*4+2] = map(binArr[i],0,1,0,255);
pixels[i*4+3] = 255;
}
console.log(pixels)
updatePixels() //here is the updatePixels function call
}
function draw() {
noLoop();
}
function factors(num) {
var half = Math.floor(num / 2),
arr = [],
i, j;
num % 2 === 0 ? (i = 2, j = 1) : (i = 3, j = 2);
for (i; i <= half; i += j) {
if(num % i === 0 && i <= num/i){
arr.push([i,num/i]);
}
}
return arr;
}
I'm very confused and any help would be much appreciated.
Please try to break your problem down into smaller steps and isolate the problem in a smaller example.
Here is an example sketch that shows the same problem:
let button;
function setup() {
createCanvas(140,140);
button = createButton("test");
button.mousePressed(updateImage);
loadPixels();
}
function updateImage(){
pixels = [];
for(let i=0; i < width * height; i++){
pixels[i*4] = 255;
pixels[i*4+1] = 0;
pixels[i*4+2] = 0;
pixels[i*4+3] = 255;
}
updatePixels();
}
function draw() {
noLoop();
}
We might expect this to turn the canvas red when we click the button, but it does not. See how this example is easier to play with, because we don't have to think about any of your logic?
Anyway, the problem is caused by this line:
pixels = [];
Take that line out, and the example program works.
My guess is this is because pixels is not a standard JavaScript array. From the reference:
Uint8ClampedArray containing the values for all the pixels in the display window.
...
Note that this is not a standard javascript array.
I wrote the following javaScript to try to draw a random path in an svg.
<script>
window.onload = function() {
function randomPath(){
var x = Math.trunc(Math.random());
var y = Math.trunc(Math.random());
var xstep = 0;
var ystep = 0;
var path = '0,0';
for (i = 0; i < 2 ; i++) {
path += ' ';
path += (x).toString();
path += ',';
path += (y).toString();
xstep = Math.random();
ystep = Math.random();
xstep *= 10;
ystep *= 10;
x += Math.trunc(xstep);
y += Math.trunc(ystep);
};
return path;
};
var figure = document.createElementNS('http://www.w3.org/2000/svg','svg');
figure.id = 'brownian-figure';
figure.setAttribute('height', '400pt');
figure.setAttribute('width', '200pt');
var pathArray = [];
for (i = 0; i < 3; i++){
pathArray[i] = document.createElementNS('http://www.w3.org/2000/svg','polyline');
};
for (i = 0; i < 3; i++){
var path = randomPath();
alert(path);
pathArray[i].setAttribute('points', path);
pathArray[i].setAttribute('style', 'fill:none;stroke:rgba(0,0,0,1);stroke-width:1');
figure.appendChild(pathArray[i]);
};
var divfigure = document.createElement('div');
divfigure.id = 'divfigure';
divfigure.style = 'margin:0pt;padding:0pt;border:0pt none;background-color:rgba(240,240,240,1);position:absolute;top:100pt;left:100pt;width:200pt;height:400pt;';
divfigure.appendChild(figure);
document.body.appendChild(divfigure);
};
</script>
Notice there are only 2 steps in the for in the randomPath() function.
The code does run and draws with that small value for the number of steps. There is an alert(path) over there to let me see the path before I try to assign it.
However, with 3 steps or more in the for inside randomPath() the code breaks. The alert(path) still shows the longer path, but the script gives the error
TypeError: pathArray[i] is undefined
pathArray[i].setAttribute('points', path);
If I replace path in the line with the error, by an explicit path (output by randomPath()), say '0,0 0,0 4,8 8,15' it still doesn't work. But if I do it and also remove the call to the randomPath() function it works again.
I am confused. It seems to be that the function randomPath() works well since the alert displays a well-formed string. The svg also works with the same string input explicitly. But the two together don't work for some reason.
What is the problem?
The problem is becoz inside last for loop you have initialise i and incremented it and then you call randomPath() which also contain i and that function increment it to 3 times so when it comes out function you have i value 3
so you get error on pathArray[i].setAttribute('points', path); changed it to k or something in randomPath() and also intialize variable as var identifier if don't want it to be global
for (i = 0; i < 3; i++){
var path = randomPath();
}
window.onload = function() {
function randomPath(){
var x = Math.trunc(Math.random());
var y = Math.trunc(Math.random());
var xstep = 0;
var ystep = 0;
var path = '0,0';
for (k = 0; k < 3 ; k++) {
path += ' ';
path += (x).toString();
path += ',';
path += (y).toString();
xstep = Math.random();
ystep = Math.random();
xstep *= 10;
ystep *= 10;
x += Math.trunc(xstep);
y += Math.trunc(ystep);
}
return path;
}
var figure = document.createElementNS('http://www.w3.org/2000/svg','svg');
figure.id = 'brownian-figure';
figure.setAttribute('height', '400pt');
figure.setAttribute('width', '200pt');
var pathArray = [];
for (j = 0; j < 3; j++){
pathArray[j] = document.createElementNS('http://www.w3.org/2000/svg','polyline');
}
for (i = 0; i < 3; i++){
var path = randomPath();
alert(path);
pathArray[i].setAttribute('points', path);
pathArray[i].setAttribute('style', 'fill:none;stroke:rgba(0,0,0,1);stroke-width:1');
figure.appendChild(pathArray[i]);
}
var divfigure = document.createElement('div');
divfigure.id = 'divfigure';
divfigure.style = 'margin:0pt;padding:0pt;border:0pt none;background-color:rgba(240,240,240,1);position:absolute;top:100pt;left:100pt;width:200pt;height:400pt;';
divfigure.appendChild(figure);
document.body.appendChild(divfigure);
};
If you guys can please review if the following approach (pseudo-code) is good to go to calcualte cosine similarity between 2 vectors:
var vectorA = [2,5,7,8];
var referenceVector= [1,1,1,1];
//Apply weights to vectors (apply positive or negative weights to elements)
var weightageVector = [1,0.5,2,1.5];
var weighted vectA = GetWeightedVector(vectorA);
//normalize each element to a value beteen 0 and 1
//#see http://stn.spotfire.com/spotfire_client_help/norm/norm_scale_between_0_and_1.htm
as calcuated here:http://jsfiddle.net/snehilw/86jqo1sm/4/
var normalizedVectorA = GetNormalizedVector(vectorA); //using the formula above
var cosineSimilarityScore = GetCosineSimilarityScore(referenceVector, normalizedVectorA );
can someone please advise if this is correct approach as this is not giving me correct results.
As requested, here is the code snippet:
var defaultVectorWeights = [1,0.5,2,1.5];
var referenceVector = [1, 1, 1, 1] //Default values for the reference vector (Do not change these);
var supportedVectorLength = referenceVector.length;
function getNormalizedVector(multiDimArray, vector){
var normalizedVector = [];
if(vector.length == supportedVectorLength){
var normalizedValue = 0;
for(var j = 0; j < supportedVectorLength ; j++){
var min = getMinMaxForMultidimensionalArrayColumn(multiDimArray,j)[0];
var max = getMinMaxForMultidimensionalArrayColumn(multiDimArray,j)[1];
normalizedValue = (max == min) ? 0.5 : (vector[j] - min) / (max - min);
normalizedVector.push(normalizedValue);
}
}
//console.log('normalizedVector='+normalizedVector);
return normalizedVector;
}
function getCosineSimilarityScore(vectorA, vectorB) {
var similarityScore;
if((vectorA.length == supportedVectorLength) && (vectorB.length == supportedVectorLength)){
var lenVectA = vectorA.length,
product = 0,
normVectorA = 0,
normVectorB = 0;
for (var i = 0; i < lenVectA ; i++) {
product += vectorA[i] * vectorB[i];
normVectorA += vectorA[i] * vectorA[i];
normVectorB += vectorB[i] * vectorB[i];
}
similarityScore = product / (Math.sqrt(normVectorA) * Math.sqrt(normVectorB));
}
else {
//TODO: Handle exception/ Fire an event to notify the server about this exception
console.log("Cosine similarity workload vectors are of unequal lengths");
}
return similarityScore;
}
function getWeightedVector(vector) {
var vectorArray = []; //Initialize
if(vector.length == supportedVectorLength){
for(var j = 0; j < supportedVectorLength ; j++){
vectorArray.push(defaultVectorWeights[j]*vector[j]);
}
}
else{
//TODO: Handle exception/ Fire an event to notify the server about this exception
console.log("Cosine similarity workload vector is of unsupported length");
}
return vectorArray;
}
function getMinMaxForMultidimensionalArrayColumn(multiDimArray, column){
var _MIN_MAX = []; //[min,max]
var columnarArray = [];
if(column < supportedVectorLength){
//Extract columnar array from the multi-dimensional array
$.map(multiDimArray, function( arrayVect) {
columnarArray.push(arrayVect[column]);
});
//Find the MIN and MAX
_MIN_MAX.push(Math.min.apply(Math,columnarArray));
_MIN_MAX.push(Math.max.apply(Math,columnarArray));
}
else{
//TODO: Handle exception/ Fire an event to notify the server about this exception
console.log("Cosine similarity workload vectors are of unequal lengths");
}
return _MIN_MAX;
}
function getAssociateWorkloadScore(multiDimArray,queryVector){
var workloadScore;
var weightedQueryVector = [];
var weightedMultiDimArr = [];
var normalizedMultiDimArr = [];
var normalizedQueryVector = [];
//Apply feature scaling
weightedQueryVector = getWeightedVector(queryVector);
weightedMultiDimArr = getWeightedMultiDimArr(multiDimArray);
normalizedQueryVector = getNormalizedVector(weightedMultiDimArr, weightedQueryVector);
workloadScore = getCosineSimilarityScore(referenceVector, normalizedQueryVector);
console.log('weightedQueryVector='+weightedQueryVector);
console.log('weightedMultiDimArr='+JSON.stringify(weightedMultiDimArr));
console.log('normalizedMultiDimArr='+JSON.stringify(normalizedMultiDimArr));
console.log('normalizedQueryVector='+normalizedQueryVector);
console.log('workloadScore='+JSON.stringify(workloadScore));
return workloadScore;
}
function getTeamWorkloadScore(multiDimArray){
var workloadScores = [];
for(var j = 0; j < multiDimArray.length ; j++){
workloadScores.push(getAssociateWorkloadScore(multiDimArray,multiDimArray[j]));
}
return workloadScores;
}
A cosine similarity is just a dot product divided by the product of norms. So why not make a dot product function and a norm function and divide the results? (dotproduct from http://c2.com/cgi/wiki?DotProductInManyProgrammingLanguages)
function dotproduct(a,b) {
var n = 0, lim = Math.min(a.length,b.length);
for (var i = 0; i < lim; i++) n += a[i] * b[i];
return n;
}
function norm2(a) {var sumsqr = 0; for (var i = 0; i < a.length; i++) sumsqr += a[i]*a[i]; return Math.sqrt(sumsqr);}
function similarity(a, b) {return dotproduct(a,b)/norm2(a)/norm2(b);}
Now similarity([1,0,0], [0,1,1]) == 0
If you necessarily need scale-invariance (i.e., the original cosine similarity), then use Gavin's code augmented with checks for zero-vectors
function cosine_sim(x, y) {
xnorm = norm2(x);
if(!xnorm) return 0;
ynorm = norm2(y);
if(!ynorm) return 0;
return dotproduct(x, y) / (xnorm * ynorm);
}
If you do not need scale-invariance, just use the dot product (i.e., cosine_sim(x, y) is dotproduct(x, y)).
I'm pretty new to programming and javascript/dom. Ultimately, I'm trying to make a sliding puzzle game but to start off with, I'm just trying to get the images loading up in a random order. It's going to be a 4x4 grid of images. The images are named Tree00, 01, 02, 03, 10, etc up to 33. Here's my code so far:
<html>
<head>
<title>Shuffle</title>
</head>
<body>
<script language="JavaScript">
<!--
Pics = new Array();
var Top = 16;
for(i = 0; i < Top; i++) {
document.write("<img><img><img><img><br>");
}
function RandomInt(Min, Max) {
RI = Math.floor(Math.random() * (Max - Min + 1)) + Min;
return(RI);
}
function Shuffle() {
N = RandomInt(0, 1);
this.Image.src=Pics[N];
this.Image.style.left = 220;
}
function ViewerObj(Image, Pics, i) {
this.Image = Image;
this.Image.style.left = 800;
this.Pics = Pics;
this.Shuffle = Shuffle;
this.Image.id = "ID" + i;
}
function Randomise() {
var i;
for(i = 0; i < Top; i++) {
Viewers[i].Shuffle();
Viewers[i].Image.style.left = 200;
}
}
Viewers = new Array();
var i;
for(i = 0; i < 3; i++) {
Pics[i] = "images/Tree" + (i) + (i + 1) + ".jpg";
}
for(i = 0; i < Top; i++) {
document.images[i].src = "images/Blank.jpg";
document.images[i].style.left = 300;
Viewers[i] = new ViewerObj(document.images[i], Pics, i);
}
//-->
</script>
<h1>Shuffle</h1>
<form>
<input type="button" value="Shuffle" onClick="Randomise();"/>
</form>
</body>
</html>
I just can't quite fathom what I need to be changing and how I'd go about it. Any help + explanation would be much appreciated. What I am trying to achieve is it loading every image but just in a random order, but with no duplicates.
Here are a few problems in your script :
first replace
for(i = 0; i < Top; i++) {
document.write("<img><img><img><img><br>");
}
by
for(i = 0; i < Top; i++) {
document.write("<img>");
if ((i+1)%4 == 0) {
document.write("<br>");
}
}
you will get only 16 IMG elements instead of 4*16 in your code
then you will need 16 different names for your images : replace
for(i = 0; i < 3; i++) {
Pics[i] = "images/Tree" + (i) + (i + 1) + ".jpg";
}
by
for(var i = 0; i < 4; i++) {
for(var j = 0; j < 4; j++) {
Pics[j+4*i] = "images/Tree" + (i) + (j) + ".jpg";
}
}
Then you biggest problem is the shuffling. You cannot shuffle the "Viewers" one-by-one because you want to avoid duplicates. Each viewer must randomly select a unique image.
For this you can use the technique in mdarwi's answer : shuffle the Pics table for instance.
check your modified code on jsbin here
If your problem is simply that you'd like the images to be properly shuffled, you can use the following code (taken directly from the Javascript sample code for the Fisher-Yates shuffle on Wikipedia:
var n = a.length;
for(var i = n - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var tmp = a[i];
a[i] = a[j];
a[j] = tmp;
}
The easiest thing to do would be to rename your images Tree1 to TreeN, and then place N integers in an array and shuffle them using the above algorithm.
(This is a code-comment, not an answer)
Pics = new Array();
The Pics variable should be declared before using. Also, use the shorthand notation []
var Pics = [];
var Top = 16;
for (i = 0; i < Top; i++) {
document.write("<img><img><img><img><br>");
}
The i variable is declared only later in the code. It should be declared at the top of the program.
function RandomInt(Min, Max) {
RI = Math.floor(Math.random() * (Max - Min + 1)) + Min;
return (RI);
}
This is particularly dangerous: the RI variable is not declared inside the function, so it becomes an implicit global property. That should be avoided. Also, the parens in the return statement are superfluous.
function RandomInt(Min, Max) {
return Math.floor(Math.random() * (Max - Min + 1)) + Min;
}
function Shuffle() {
N = RandomInt(0, 1);
this.Image.src = Pics[N];
this.Image.style.left = 220;
}
Again, the N variable should be declared. Also, why is this "method" declared outside of the ViewerObj constructor? Either, put it inside, or - even better - add it to the constructors prototype object. That way, there will only be one Shuffle function object instead of many.
function ViewerObj(Image, Pics, i) {
this.Image = Image;
this.Image.style.left = 800;
this.Pics = Pics;
this.Shuffle = Shuffle;
this.Image.id = "ID" + i;
}
function Randomise() {
var i;
for (i = 0; i < Top; i++) {
Viewers[i].Shuffle();
Viewers[i].Image.style.left = 200;
}
}
Viewers = new Array();
var i;
As mentioned above, the i variable should be declared on top. Also, the Viewers variable should be declared.
var Viewers = [];
for (i = 0; i < 3; i++) {
Pics[i] = "images/Tree" + (i) + (i + 1) + ".jpg";
}
for (i = 0; i < Top; i++) {
document.images[i].src = "images/Blank.jpg";
document.images[i].style.left = 300;
Viewers[i] = new ViewerObj(document.images[i], Pics, i);
}