From 7edb855519ad63bc7b47185fd299f02b1e200453 Mon Sep 17 00:00:00 2001 From: Mikael Souza Date: Wed, 7 Feb 2018 16:19:43 -0400 Subject: [PATCH 01/38] Fixed typo --- TODO.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/TODO.md b/TODO.md index 152c27e..2d2b859 100644 --- a/TODO.md +++ b/TODO.md @@ -1,10 +1,10 @@ # To-Do List -* [x] Redo gradient descent video about -* [x] delta weight formulas, connect to "mathematics of gradient" video -* [x] Impelment gradient descent in library / with code -* [ ] XOR coding challenge -* [ ] MNIST coding challenge -* [ ] Support for multiple hidden layers -* [ ] Support for different activation functions +* [x] Redo gradient descent video about. +* [x] Delta weight formulas, connect to "mathematics of gradient" video. +* [x] Implement gradient descent in library / with code. +* [ ] XOR coding challenge. +* [ ] MNIST coding challenge. +* [ ] Support for multiple hidden layers. +* [ ] Support for different activation functions. * [ ] Combine with ml5 / deeplearnjs. From 5ecde149e37194c4a377f9e9f1c5a060c4b9c11f Mon Sep 17 00:00:00 2001 From: JonasFovea <30690893+JonasFovea@users.noreply.github.com> Date: Wed, 7 Feb 2018 21:22:31 +0100 Subject: [PATCH 02/38] Coding Challenge: the "Hello World" of NN - handwritten number recognition --- TODO.md | 1 + 1 file changed, 1 insertion(+) diff --git a/TODO.md b/TODO.md index 152c27e..99e9c47 100644 --- a/TODO.md +++ b/TODO.md @@ -8,3 +8,4 @@ * [ ] Support for multiple hidden layers * [ ] Support for different activation functions * [ ] Combine with ml5 / deeplearnjs. +* [ ] Coding Challenge: the "Hello World" of NN - handwritten number recognition From d39fc1644c7aa8ca5bafd60a6517c81c560eb930 Mon Sep 17 00:00:00 2001 From: Phil Turner Date: Wed, 7 Feb 2018 20:55:12 +0000 Subject: [PATCH 03/38] Fix video repo link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3cba087..b8be127 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Neural Network JavaScript library for Coding Train tutorials ## Getting Started -If you're looking for the original source code to match the videos [visit this repo] (https://github.com/CodingTrain/Rainbow-Code/tree/master/Courses/natureofcode/10.18-toy_neural_network) +If you're looking for the original source code to match the videos [visit this repo](https://github.com/CodingTrain/Rainbow-Code/tree/master/Courses/natureofcode/10.18-toy_neural_network) TODO From fa362fbaa21a6e5e906f21a14c8c0ca8a85dbab0 Mon Sep 17 00:00:00 2001 From: mdatsev Date: Wed, 7 Feb 2018 22:57:13 +0200 Subject: [PATCH 04/38] Moved the To-Do list in the README --- README.md | 13 ++++++++++++- TODO.md | 10 ---------- 2 files changed, 12 insertions(+), 11 deletions(-) delete mode 100644 TODO.md diff --git a/README.md b/README.md index 3cba087..4211d96 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,20 @@ Neural Network JavaScript library for Coding Train tutorials +## To-Do List + +* [x] Redo gradient descent video about +* [x] delta weight formulas, connect to "mathematics of gradient" video +* [x] Impelment gradient descent in library / with code +* [ ] XOR coding challenge +* [ ] MNIST coding challenge +* [ ] Support for multiple hidden layers +* [ ] Support for different activation functions +* [ ] Combine with ml5 / deeplearnjs + ## Getting Started -If you're looking for the original source code to match the videos [visit this repo] (https://github.com/CodingTrain/Rainbow-Code/tree/master/Courses/natureofcode/10.18-toy_neural_network) +If you're looking for the original source code to match the videos [visit this repo](https://github.com/CodingTrain/Rainbow-Code/tree/master/Courses/natureofcode/10.18-toy_neural_network) TODO diff --git a/TODO.md b/TODO.md deleted file mode 100644 index 152c27e..0000000 --- a/TODO.md +++ /dev/null @@ -1,10 +0,0 @@ -# To-Do List - -* [x] Redo gradient descent video about -* [x] delta weight formulas, connect to "mathematics of gradient" video -* [x] Impelment gradient descent in library / with code -* [ ] XOR coding challenge -* [ ] MNIST coding challenge -* [ ] Support for multiple hidden layers -* [ ] Support for different activation functions -* [ ] Combine with ml5 / deeplearnjs. From 59509bdc485b1e19ffa6298c7b38e241d0fad03e Mon Sep 17 00:00:00 2001 From: narch Date: Wed, 7 Feb 2018 23:01:05 -0500 Subject: [PATCH 05/38] Adding to README the CircleCi Build Status. This is much more appealing !!!! Keep up the good work. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b8be127..2023d21 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Toy-Neural-Network-JS +# Toy-Neural-Network-JS ![Build Status](https://circleci.com/gh/CodingTrain/Toy-Neural-Network-JS.png?&style=shield&circle-token=:circle-token) Neural Network JavaScript library for Coding Train tutorials From 91625d1c499381c318c442ee63011810609e8541 Mon Sep 17 00:00:00 2001 From: mdatsev Date: Thu, 8 Feb 2018 17:35:16 +0200 Subject: [PATCH 06/38] Updated To-Do List --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4211d96..3bbc7df 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,18 @@ -# Toy-Neural-Network-JS +# Toy-Neural-Network-JS ![Build Status](https://circleci.com/gh/CodingTrain/Toy-Neural-Network-JS.png?&style=shield&circle-token=:circle-token) Neural Network JavaScript library for Coding Train tutorials ## To-Do List * [x] Redo gradient descent video about -* [x] delta weight formulas, connect to "mathematics of gradient" video -* [x] Impelment gradient descent in library / with code +* [x] Delta weight formulas, connect to "mathematics of gradient" video +* [x] Implement gradient descent in library / with code * [ ] XOR coding challenge * [ ] MNIST coding challenge * [ ] Support for multiple hidden layers * [ ] Support for different activation functions * [ ] Combine with ml5 / deeplearnjs +* [ ] Coding Challenge: the "Hello World" of NN - handwritten number recognition ## Getting Started From 8e0057cddf765226640aacd4c26876eb6b2512ef Mon Sep 17 00:00:00 2001 From: Maik1999 Date: Fri, 9 Feb 2018 14:35:06 +0100 Subject: [PATCH 07/38] Added error for elemntwise addition, subbtraction and hadamard product when the dimensions of the matrices don't match. Also added tests for them. --- lib/matrix.js | 16 +++++++++++++++- lib/matrix.test.js | 38 +++++++++++++++++++++++++++++++++++++- 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/lib/matrix.js b/lib/matrix.js index 0de7dd9..a3a2686 100644 --- a/lib/matrix.js +++ b/lib/matrix.js @@ -13,6 +13,11 @@ class Matrix { } static subtract(a, b) { + if(a.rows!==b.rows || a.cols!==b.cols){ + console.log('Columns and Rows of A must match Columns and Rows of B.'); + return; + } + // Return a new Matrix a-b return new Matrix(a.rows, a.cols) .map((_, i, j) => a.data[i][j] - b.data[i][j]); @@ -34,7 +39,11 @@ class Matrix { add(n) { if (n instanceof Matrix) { - return this.map((e, i, j) => e + n.data[i][j]); + if(this.rows!==n.rows || this.cols!==n.cols){ + console.log('Columns and Rows of A must match Columns and Rows of B.'); + return; + } + return this.map((e, i, j) => e + n.data[i][j]); } else { return this.map(e => e + n); } @@ -65,6 +74,11 @@ class Matrix { multiply(n) { if (n instanceof Matrix) { + if(this.rows!==n.rows || this.cols!==n.cols){ + console.log('Columns and Rows of A must match Columns and Rows of B.'); + return; + } + // hadamard product return this.map((e, i, j) => e * n.data[i][j]); } else { diff --git a/lib/matrix.test.js b/lib/matrix.test.js index 603155c..26eb67b 100644 --- a/lib/matrix.test.js +++ b/lib/matrix.test.js @@ -223,6 +223,42 @@ test('mapping with instance map', () => { }); }); +test('error handling of addition when columns and rows of A don\'t match columns and rows of B.', () => { + //Replace console.log with a jest mock so we can see if it has been called + global.console.log = jest.fn(); + + let m1 = new Matrix(1, 2); + let m2 = new Matrix(3, 4); + m1.add(m2); + + //Check if the mock console.log has been called + expect(global.console.log).toHaveBeenCalledWith('Columns and Rows of A must match Columns and Rows of B.') +}); + +test('error handling of static subtraction when columns and rows of A don\'t match columns and rows of B.', () => { + //Replace console.log with a jest mock so we can see if it has been called + global.console.log = jest.fn(); + + let m1 = new Matrix(1, 2); + let m2 = new Matrix(3, 4); + Matrix.subtract(m1,m2); + + //Check if the mock console.log has been called + expect(global.console.log).toHaveBeenCalledWith('Columns and Rows of A must match Columns and Rows of B.') +}); + +test('error handling of hadamard product when columns and rows of A don\'t match columns and rows of B.', () => { + //Replace console.log with a jest mock so we can see if it has been called + global.console.log = jest.fn(); + + let m1 = new Matrix(1, 2); + let m2 = new Matrix(3, 4); + m1.multiply(m2); + + //Check if the mock console.log has been called + expect(global.console.log).toHaveBeenCalledWith('Columns and Rows of A must match Columns and Rows of B.') +}); + test('error handling of matrix product when columns of A don\'t match rows of B.', () => { //Replace console.log with a jest mock so we can see if it has been called global.console.log = jest.fn(); @@ -231,7 +267,7 @@ test('error handling of matrix product when columns of A don\'t match rows of B. let m2 = new Matrix(3, 4); Matrix.multiply(m1, m2); - //Check if the mock console.log has been called + //Check if the mock console.log has been called expect(global.console.log).toHaveBeenCalledWith('Columns of A must match rows of B.') }); From b59d42738fa25f55304c12d4404427bc437ebb24 Mon Sep 17 00:00:00 2001 From: schrummy14 Date: Fri, 9 Feb 2018 11:11:16 -0600 Subject: [PATCH 08/38] Give frame work to add additional activation functions --- lib/nn.js | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/lib/nn.js b/lib/nn.js index 1458703..df71d05 100644 --- a/lib/nn.js +++ b/lib/nn.js @@ -26,6 +26,10 @@ class NeuralNetwork { this.bias_h.randomize(); this.bias_o.randomize(); this.setLearningRate(); + + this.setActivationFunction(); + this.setDActivationFunction(); + } predict(input_array) { @@ -35,12 +39,12 @@ class NeuralNetwork { let hidden = Matrix.multiply(this.weights_ih, inputs); hidden.add(this.bias_h); // activation function! - hidden.map(sigmoid); + hidden.map(this.activation_function); // Generating the output's output! let output = Matrix.multiply(this.weights_ho, hidden); output.add(this.bias_o); - output.map(sigmoid); + output.map(this.activation_function); // Sending back to the caller! return output.toArray(); @@ -49,6 +53,14 @@ class NeuralNetwork { setLearningRate(learning_rate = 0.1) { this.learning_rate = learning_rate; } + + setActivationFunction(Fun = sigmoid) { + this.activation_function = Fun; + } + + setDActivationFunction(dFun = dsigmoid) { + this.d_activation_function = dFun; + } train(input_array, target_array) { // Generating the Hidden Outputs @@ -56,12 +68,12 @@ class NeuralNetwork { let hidden = Matrix.multiply(this.weights_ih, inputs); hidden.add(this.bias_h); // activation function! - hidden.map(sigmoid); + hidden.map(this.activation_function); // Generating the output's output! let outputs = Matrix.multiply(this.weights_ho, hidden); outputs.add(this.bias_o); - outputs.map(sigmoid); + outputs.map(this.activation_function); // Convert array to matrix object let targets = Matrix.fromArray(target_array); @@ -72,7 +84,7 @@ class NeuralNetwork { // let gradient = outputs * (1 - outputs); // Calculate gradient - let gradients = Matrix.map(outputs, dsigmoid); + let gradients = Matrix.map(outputs, this.d_activation_function); gradients.multiply(output_errors); gradients.multiply(this.learning_rate); @@ -91,7 +103,7 @@ class NeuralNetwork { let hidden_errors = Matrix.multiply(who_t, output_errors); // Calculate hidden gradient - let hidden_gradient = Matrix.map(hidden, dsigmoid); + let hidden_gradient = Matrix.map(hidden, this.d_activation_function); hidden_gradient.multiply(hidden_errors); hidden_gradient.multiply(this.learning_rate); From 1ac89ae263a6c66775fdda8cf03e52f43cfad654 Mon Sep 17 00:00:00 2001 From: Daniel Shiffman Date: Fri, 9 Feb 2018 14:29:03 -0500 Subject: [PATCH 09/38] new mnist example --- examples/mnist/file.txt | 3 + examples/mnist/index.html | 52 + examples/mnist/libraries/p5.dom.js | 2536 + examples/mnist/libraries/p5.js | 71406 +++++++++++++++++++++++ examples/mnist/libraries/p5.sound.js | 10520 ++++ examples/mnist/mnist.js | 25 + examples/mnist/sketch.js | 179 + examples/mnist/t10k-images-idx3-ubyte | Bin 0 -> 7840016 bytes examples/mnist/t10k-labels-idx1-ubyte | Bin 0 -> 10008 bytes examples/mnist/train-images-idx3-ubyte | Bin 0 -> 47040016 bytes examples/mnist/train-labels-idx1-ubyte | Bin 0 -> 60008 bytes 11 files changed, 84721 insertions(+) create mode 100644 examples/mnist/file.txt create mode 100644 examples/mnist/index.html create mode 100644 examples/mnist/libraries/p5.dom.js create mode 100644 examples/mnist/libraries/p5.js create mode 100644 examples/mnist/libraries/p5.sound.js create mode 100644 examples/mnist/mnist.js create mode 100644 examples/mnist/sketch.js create mode 100644 examples/mnist/t10k-images-idx3-ubyte create mode 100644 examples/mnist/t10k-labels-idx1-ubyte create mode 100644 examples/mnist/train-images-idx3-ubyte create mode 100644 examples/mnist/train-labels-idx1-ubyte diff --git a/examples/mnist/file.txt b/examples/mnist/file.txt new file mode 100644 index 0000000..6760354 --- /dev/null +++ b/examples/mnist/file.txt @@ -0,0 +1,3 @@ +on +tw +th diff --git a/examples/mnist/index.html b/examples/mnist/index.html new file mode 100644 index 0000000..418696b --- /dev/null +++ b/examples/mnist/index.html @@ -0,0 +1,52 @@ + + + + + + + + mnist + + + + + + + + + + + + + + + + + +
+ +

+ + +
+ Testing:
+
+ User Guess: +

+ + + diff --git a/examples/mnist/libraries/p5.dom.js b/examples/mnist/libraries/p5.dom.js new file mode 100644 index 0000000..334321c --- /dev/null +++ b/examples/mnist/libraries/p5.dom.js @@ -0,0 +1,2536 @@ +/*! p5.dom.js v0.3.4 Aug 11, 2017 */ +/** + *

The web is much more than just canvas and p5.dom makes it easy to interact + * with other HTML5 objects, including text, hyperlink, image, input, video, + * audio, and webcam.

+ *

There is a set of creation methods, DOM manipulation methods, and + * an extended p5.Element that supports a range of HTML elements. See the + * + * beyond the canvas tutorial for a full overview of how this addon works. + * + *

Methods and properties shown in black are part of the p5.js core, items in + * blue are part of the p5.dom library. You will need to include an extra file + * in order to access the blue functions. See the + * using a library + * section for information on how to include this library. p5.dom comes with + * p5 complete or you can download the single file + * + * here.

+ *

See tutorial: beyond the canvas + * for more info on how to use this libary. + * + * @module p5.dom + * @submodule p5.dom + * @for p5.dom + * @main + */ + +(function (root, factory) { + if (typeof define === 'function' && define.amd) + define('p5.dom', ['p5'], function (p5) { (factory(p5));}); + else if (typeof exports === 'object') + factory(require('../p5')); + else + factory(root['p5']); +}(this, function (p5) { + +// ============================================================================= +// p5 additions +// ============================================================================= + + /** + * Searches the page for an element with the given ID, class, or tag name (using the '#' or '.' + * prefixes to specify an ID or class respectively, and none for a tag) and returns it as + * a p5.Element. If a class or tag name is given with more than 1 element, + * only the first element will be returned. + * The DOM node itself can be accessed with .elt. + * Returns null if none found. You can also specify a container to search within. + * + * @method select + * @param {String} name id, class, or tag name of element to search for + * @param {String} [container] id, p5.Element, or HTML element to search within + * @return {Object|p5.Element|Null} p5.Element containing node found + * @example + *

+ * function setup() { + * createCanvas(100,100); + * //translates canvas 50px down + * select('canvas').position(100, 100); + * } + *
+ *
+ * // these are all valid calls to select() + * var a = select('#moo'); + * var b = select('#blah', '#myContainer'); + * var c = select('#foo', b); + * var d = document.getElementById('beep'); + * var e = select('p', d); + *
+ * + */ + p5.prototype.select = function (e, p) { + var res = null; + var container = getContainer(p); + if (e[0] === '.'){ + e = e.slice(1); + res = container.getElementsByClassName(e); + if (res.length) { + res = res[0]; + } else { + res = null; + } + }else if (e[0] === '#'){ + e = e.slice(1); + res = container.getElementById(e); + }else { + res = container.getElementsByTagName(e); + if (res.length) { + res = res[0]; + } else { + res = null; + } + } + if (res) { + return wrapElement(res); + } else { + return null; + } + }; + + /** + * Searches the page for elements with the given class or tag name (using the '.' prefix + * to specify a class and no prefix for a tag) and returns them as p5.Elements + * in an array. + * The DOM node itself can be accessed with .elt. + * Returns an empty array if none found. + * You can also specify a container to search within. + * + * @method selectAll + * @param {String} name class or tag name of elements to search for + * @param {String} [container] id, p5.Element, or HTML element to search within + * @return {Array} Array of p5.Elements containing nodes found + * @example + *
+ * function setup() { + * createButton('btn'); + * createButton('2nd btn'); + * createButton('3rd btn'); + * var buttons = selectAll('button'); + * + * for (var i = 0; i < buttons.length; i++){ + * buttons[i].size(100,100); + * } + * } + *
+ *
+ * // these are all valid calls to selectAll() + * var a = selectAll('.moo'); + * var b = selectAll('div'); + * var c = selectAll('button', '#myContainer'); + * var d = select('#container'); + * var e = selectAll('p', d); + * var f = document.getElementById('beep'); + * var g = select('.blah', f); + *
+ * + */ + p5.prototype.selectAll = function (e, p) { + var arr = []; + var res; + var container = getContainer(p); + if (e[0] === '.'){ + e = e.slice(1); + res = container.getElementsByClassName(e); + } else { + res = container.getElementsByTagName(e); + } + if (res) { + for (var j = 0; j < res.length; j++) { + var obj = wrapElement(res[j]); + arr.push(obj); + } + } + return arr; + }; + + /** + * Helper function for select and selectAll + */ + function getContainer(p) { + var container = document; + if (typeof p === 'string' && p[0] === '#'){ + p = p.slice(1); + container = document.getElementById(p) || document; + } else if (p instanceof p5.Element){ + container = p.elt; + } else if (p instanceof HTMLElement){ + container = p; + } + return container; + } + + /** + * Helper function for getElement and getElements. + */ + function wrapElement(elt) { + if(elt.tagName === "INPUT" && elt.type === "checkbox") { + var converted = new p5.Element(elt); + converted.checked = function(){ + if (arguments.length === 0){ + return this.elt.checked; + } else if(arguments[0]) { + this.elt.checked = true; + } else { + this.elt.checked = false; + } + return this; + }; + return converted; + } else if (elt.tagName === "VIDEO" || elt.tagName === "AUDIO") { + return new p5.MediaElement(elt); + } else if ( elt.tagName === "SELECT" ){ + return createSelect( new p5.Element(elt) ); + } + else { + return new p5.Element(elt); + } + } + + /** + * Removes all elements created by p5, except any canvas / graphics + * elements created by createCanvas or createGraphics. + * Event handlers are removed, and element is removed from the DOM. + * @method removeElements + * @example + *
+ * function setup() { + * createCanvas(100, 100); + * createDiv('this is some text'); + * createP('this is a paragraph'); + * } + * function mousePressed() { + * removeElements(); // this will remove the div and p, not canvas + * } + *
+ * + */ + p5.prototype.removeElements = function (e) { + for (var i=0; i + * var myDiv; + * function setup() { + * myDiv = createDiv('this is some text'); + * } + * + */ + + /** + * Creates a <p></p> element in the DOM with given inner HTML. Used + * for paragraph length text. + * Appends to the container node if one is specified, otherwise + * appends to body. + * + * @method createP + * @param {String} [html] inner HTML for element created + * @return {Object|p5.Element} pointer to p5.Element holding created node + * @example + *
+ * var myP; + * function setup() { + * myP = createP('this is some text'); + * } + *
+ */ + + /** + * Creates a <span></span> element in the DOM with given inner HTML. + * Appends to the container node if one is specified, otherwise + * appends to body. + * + * @method createSpan + * @param {String} [html] inner HTML for element created + * @return {Object|p5.Element} pointer to p5.Element holding created node + * @example + *
+ * var mySpan; + * function setup() { + * mySpan = createSpan('this is some text'); + * } + *
+ */ + var tags = ['div', 'p', 'span']; + tags.forEach(function(tag) { + var method = 'create' + tag.charAt(0).toUpperCase() + tag.slice(1); + p5.prototype[method] = function(html) { + var elt = document.createElement(tag); + elt.innerHTML = typeof html === undefined ? "" : html; + return addElement(elt, this); + } + }); + + /** + * Creates an <img> element in the DOM with given src and + * alternate text. + * Appends to the container node if one is specified, otherwise + * appends to body. + * + * @method createImg + * @param {String} src src path or url for image + * @param {String} [alt] alternate text to be used if image does not load + * @param {Function} [successCallback] callback to be called once image data is loaded + * @return {Object|p5.Element} pointer to p5.Element holding created node + * @example + *
+ * var img; + * function setup() { + * img = createImg('http://p5js.org/img/asterisk-01.png'); + * } + *
+ */ + p5.prototype.createImg = function() { + var elt = document.createElement('img'); + var args = arguments; + var self; + var setAttrs = function(){ + self.width = elt.offsetWidth || elt.width; + self.height = elt.offsetHeight || elt.height; + if (args.length > 1 && typeof args[1] === 'function'){ + self.fn = args[1]; + self.fn(); + }else if (args.length > 1 && typeof args[2] === 'function'){ + self.fn = args[2]; + self.fn(); + } + }; + elt.src = args[0]; + if (args.length > 1 && typeof args[1] === 'string'){ + elt.alt = args[1]; + } + elt.onload = function(){ + setAttrs(); + } + self = addElement(elt, this); + return self; + }; + + /** + * Creates an <a></a> element in the DOM for including a hyperlink. + * Appends to the container node if one is specified, otherwise + * appends to body. + * + * @method createA + * @param {String} href url of page to link to + * @param {String} html inner html of link element to display + * @param {String} [target] target where new link should open, + * could be _blank, _self, _parent, _top. + * @return {Object|p5.Element} pointer to p5.Element holding created node + * @example + *
+ * var myLink; + * function setup() { + * myLink = createA('http://p5js.org/', 'this is a link'); + * } + *
+ */ + p5.prototype.createA = function(href, html, target) { + var elt = document.createElement('a'); + elt.href = href; + elt.innerHTML = html; + if (target) elt.target = target; + return addElement(elt, this); + }; + + /** INPUT **/ + + + /** + * Creates a slider <input></input> element in the DOM. + * Use .size() to set the display length of the slider. + * Appends to the container node if one is specified, otherwise + * appends to body. + * + * @method createSlider + * @param {Number} min minimum value of the slider + * @param {Number} max maximum value of the slider + * @param {Number} [value] default value of the slider + * @param {Number} [step] step size for each tick of the slider (if step is set to 0, the slider will move continuously from the minimum to the maximum value) + * @return {Object|p5.Element} pointer to p5.Element holding created node + * @example + *
+ * var slider; + * function setup() { + * slider = createSlider(0, 255, 100); + * slider.position(10, 10); + * slider.style('width', '80px'); + * } + * + * function draw() { + * var val = slider.value(); + * background(val); + * } + *
+ * + *
+ * var slider; + * function setup() { + * colorMode(HSB); + * slider = createSlider(0, 360, 60, 40); + * slider.position(10, 10); + * slider.style('width', '80px'); + * } + * + * function draw() { + * var val = slider.value(); + * background(val, 100, 100, 1); + * } + *
+ */ + p5.prototype.createSlider = function(min, max, value, step) { + var elt = document.createElement('input'); + elt.type = 'range'; + elt.min = min; + elt.max = max; + if (step === 0) { + elt.step = .000000000000000001; // smallest valid step + } else if (step) { + elt.step = step; + } + if (typeof(value) === "number") elt.value = value; + return addElement(elt, this); + }; + + /** + * Creates a <button></button> element in the DOM. + * Use .size() to set the display size of the button. + * Use .mousePressed() to specify behavior on press. + * Appends to the container node if one is specified, otherwise + * appends to body. + * + * @method createButton + * @param {String} label label displayed on the button + * @param {String} [value] value of the button + * @return {Object|p5.Element} pointer to p5.Element holding created node + * @example + *
+ * var button; + * function setup() { + * createCanvas(100, 100); + * background(0); + * button = createButton('click me'); + * button.position(19, 19); + * button.mousePressed(changeBG); + * } + * + * function changeBG() { + * var val = random(255); + * background(val); + * } + *
+ */ + p5.prototype.createButton = function(label, value) { + var elt = document.createElement('button'); + elt.innerHTML = label; + if (value) elt.value = value; + return addElement(elt, this); + }; + + /** + * Creates a checkbox <input></input> element in the DOM. + * Calling .checked() on a checkbox returns if it is checked or not + * + * @method createCheckbox + * @param {String} [label] label displayed after checkbox + * @param {boolean} [value] value of the checkbox; checked is true, unchecked is false.Unchecked if no value given + * @return {Object|p5.Element} pointer to p5.Element holding created node + * @example + *
+ * var checkbox; + * + * function setup() { + * checkbox = createCheckbox('label', false); + * checkbox.changed(myCheckedEvent); + * } + * + * function myCheckedEvent() { + * if (this.checked()) { + * console.log("Checking!"); + * } else { + * console.log("Unchecking!"); + * } + * } + *
+ */ + p5.prototype.createCheckbox = function() { + var elt = document.createElement('div'); + var checkbox = document.createElement('input'); + checkbox.type = 'checkbox'; + elt.appendChild(checkbox); + //checkbox must be wrapped in p5.Element before label so that label appears after + var self = addElement(elt, this); + self.checked = function(){ + var cb = self.elt.getElementsByTagName('input')[0]; + if (cb) { + if (arguments.length === 0){ + return cb.checked; + }else if(arguments[0]){ + cb.checked = true; + }else{ + cb.checked = false; + } + } + return self; + }; + this.value = function(val){ + self.value = val; + return this; + }; + if (arguments[0]){ + var ran = Math.random().toString(36).slice(2); + var label = document.createElement('label'); + checkbox.setAttribute('id', ran); + label.htmlFor = ran; + self.value(arguments[0]); + label.appendChild(document.createTextNode(arguments[0])); + elt.appendChild(label); + } + if (arguments[1]){ + checkbox.checked = true; + } + return self; + }; + + /** + * Creates a dropdown menu <select></select> element in the DOM. + * It also helps to assign select-box methods to p5.Element when selecting existing select box + * @method createSelect + * @param {boolean} [multiple] true if dropdown should support multiple selections + * @return {p5.Element} + * @example + *
+ * var sel; + * + * function setup() { + * textAlign(CENTER); + * background(200); + * sel = createSelect(); + * sel.position(10, 10); + * sel.option('pear'); + * sel.option('kiwi'); + * sel.option('grape'); + * sel.changed(mySelectEvent); + * } + * + * function mySelectEvent() { + * var item = sel.value(); + * background(200); + * text("it's a "+item+"!", 50, 50); + * } + *
+ */ + /** + * @method createSelect + * @param {Object} existing DOM select element + * @return {p5.Element} + */ + + p5.prototype.createSelect = function() { + var elt, self; + var arg = arguments[0]; + if( typeof arg === 'object' && arg.elt.nodeName === 'SELECT' ) { + self = arg; + elt = this.elt = arg.elt; + } else { + elt = document.createElement('select'); + if( arg && typeof arg === 'boolean' ) { + elt.setAttribute('multiple', 'true'); + } + self = addElement(elt, this); + } + self.option = function(name, value) { + var index; + //see if there is already an option with this name + for (var i = 0; i < this.elt.length; i++) { + if(this.elt[i].innerHTML == name) { + index = i; + break; + } + } + //if there is an option with this name we will modify it + if(index !== undefined) { + //if the user passed in false then delete that option + if(value === false) { + this.elt.remove(index); + } else { + //otherwise if the name and value are the same then change both + if(this.elt[index].innerHTML == this.elt[index].value) { + this.elt[index].innerHTML = this.elt[index].value = value; + //otherwise just change the value + } else { + this.elt[index].value = value; + } + } + } + //if it doesn't exist make it + else { + var opt = document.createElement('option'); + opt.innerHTML = name; + if (arguments.length > 1) + opt.value = value; + else + opt.value = name; + elt.appendChild(opt); + } + }; + self.selected = function(value) { + var arr = []; + if (arguments.length > 0) { + for (var i = 0; i < this.elt.length; i++) { + if (value.toString() === this.elt[i].value) { + this.elt.selectedIndex = i; + } + } + return this; + } else { + if (this.elt.getAttribute('multiple')) { + for (var i = 0; i < this.elt.selectedOptions.length; i++) { + arr.push(this.elt.selectedOptions[i].value); + } + return arr; + } else { + return this.elt.value; + } + } + }; + return self; + }; + + /** + * Creates a radio button <input></input> element in the DOM. + * The .option() method can be used to set options for the radio after it is + * created. The .value() method will return the currently selected option. + * + * @method createRadio + * @param {String} [divId] the id and name of the created div and input field respectively + * @return {Object|p5.Element} pointer to p5.Element holding created node + * @example + *
+ * var radio; + * + * function setup() { + * radio = createRadio(); + * radio.option("black"); + * radio.option("white"); + * radio.option("gray"); + * radio.style('width', '60px'); + * textAlign(CENTER); + * fill(255, 0, 0); + * } + * + * function draw() { + * var val = radio.value(); + * background(val); + * text(val, width/2, height/2); + * } + *
+ *
+ * var radio; + * + * function setup() { + * radio = createRadio(); + * radio.option('apple', 1); + * radio.option('bread', 2); + * radio.option('juice', 3); + * radio.style('width', '60px'); + * textAlign(CENTER); + * } + * + * function draw() { + * background(200); + * var val = radio.value(); + * if (val) { + * text('item cost is $'+val, width/2, height/2); + * } + * } + *
+ */ + p5.prototype.createRadio = function() { + var radios = document.querySelectorAll("input[type=radio]"); + var count = 0; + if(radios.length > 1){ + var length = radios.length; + var prev=radios[0].name; + var current = radios[1].name; + count = 1; + for(var i = 1; i < length; i++) { + current = radios[i].name; + if(prev != current){ + count++; + } + prev = current; + } + } + else if (radios.length == 1){ + count = 1; + } + var elt = document.createElement('div'); + var self = addElement(elt, this); + var times = -1; + self.option = function(name, value){ + var opt = document.createElement('input'); + opt.type = 'radio'; + opt.innerHTML = name; + if (arguments.length > 1) + opt.value = value; + else + opt.value = name; + opt.setAttribute('name',"defaultradio"+count); + elt.appendChild(opt); + if (name){ + times++; + var ran = Math.random().toString(36).slice(2); + var label = document.createElement('label'); + opt.setAttribute('id', "defaultradio"+count+"-"+times); + label.htmlFor = "defaultradio"+count+"-"+times; + label.appendChild(document.createTextNode(name)); + elt.appendChild(label); + } + return opt; + }; + self.selected = function(){ + var length = this.elt.childNodes.length; + if(arguments.length == 1) { + for (var i = 0; i < length; i+=2){ + if(this.elt.childNodes[i].value == arguments[0]) + this.elt.childNodes[i].checked = true; + } + return this; + } else { + for (var i = 0; i < length; i+=2){ + if(this.elt.childNodes[i].checked == true) + return this.elt.childNodes[i].value; + } + } + }; + self.value = function(){ + var length = this.elt.childNodes.length; + if(arguments.length == 1) { + for (var i = 0; i < length; i+=2){ + if(this.elt.childNodes[i].value == arguments[0]) + this.elt.childNodes[i].checked = true; + } + return this; + } else { + for (var i = 0; i < length; i+=2){ + if(this.elt.childNodes[i].checked == true) + return this.elt.childNodes[i].value; + } + return ""; + } + }; + return self + }; + + /** + * Creates an <input></input> element in the DOM for text input. + * Use .size() to set the display length of the box. + * Appends to the container node if one is specified, otherwise + * appends to body. + * + * @method createInput + * @param {Number} [value] default value of the input box + * @param {String} [type] type of text, ie text, password etc. Defaults to text + * @return {Object|p5.Element} pointer to p5.Element holding created node + * @example + *
+ * function setup(){ + * var inp = createInput(''); + * inp.input(myInputEvent); + * } + * + * function myInputEvent(){ + * console.log('you are typing: ', this.value()); + * } + * + *
+ */ + p5.prototype.createInput = function(value, type) { + var elt = document.createElement('input'); + elt.type = type ? type : 'text'; + if (value) elt.value = value; + return addElement(elt, this); + }; + + /** + * Creates an <input></input> element in the DOM of type 'file'. + * This allows users to select local files for use in a sketch. + * + * @method createFileInput + * @param {Function} [callback] callback function for when a file loaded + * @param {String} [multiple] optional to allow multiple files selected + * @return {Object|p5.Element} pointer to p5.Element holding created DOM element + * @example + * var input; + * var img; + * + * function setup() { + * input = createFileInput(handleFile); + * input.position(0, 0); + * } + * + * function draw() { + * if (img) { + * image(img, 0, 0, width, height); + * } + * } + * + * function handleFile(file) { + * print(file); + * if (file.type === 'image') { + * img = createImg(file.data); + * img.hide(); + * } + * } + */ + p5.prototype.createFileInput = function(callback, multiple) { + + // Is the file stuff supported? + if (window.File && window.FileReader && window.FileList && window.Blob) { + // Yup, we're ok and make an input file selector + var elt = document.createElement('input'); + elt.type = 'file'; + + // If we get a second argument that evaluates to true + // then we are looking for multiple files + if (multiple) { + // Anything gets the job done + elt.multiple = 'multiple'; + } + + // Function to handle when a file is selected + // We're simplifying life and assuming that we always + // want to load every selected file + function handleFileSelect(evt) { + // These are the files + var files = evt.target.files; + // Load each one and trigger a callback + for (var i = 0; i < files.length; i++) { + var f = files[i]; + var reader = new FileReader(); + function makeLoader(theFile) { + // Making a p5.File object + var p5file = new p5.File(theFile); + return function(e) { + p5file.data = e.target.result; + callback(p5file); + }; + }; + reader.onload = makeLoader(f); + + // Text or data? + // This should likely be improved + if (f.type.indexOf('text') > -1) { + reader.readAsText(f); + } else { + reader.readAsDataURL(f); + } + } + } + + // Now let's handle when a file was selected + elt.addEventListener('change', handleFileSelect, false); + return addElement(elt, this); + } else { + console.log('The File APIs are not fully supported in this browser. Cannot create element.'); + } + }; + + + /** VIDEO STUFF **/ + + function createMedia(pInst, type, src, callback) { + var elt = document.createElement(type); + + // allow src to be empty + var src = src || ''; + if (typeof src === 'string') { + src = [src]; + } + for (var i=0; i