diff --git a/src/app.js b/src/app.js index 8da1db8c75..19d193ff5a 100644 --- a/src/app.js +++ b/src/app.js @@ -46,6 +46,8 @@ require('./core/transform'); require('./typography/attributes'); require('./typography/loading_displaying'); +require('./data/p5.TypedDict'); + require('./webgl/p5.RendererGL'); require('./webgl/p5.Geometry'); require('./webgl/p5.RendererGL.Retained'); diff --git a/src/data/p5.TypedDict.js b/src/data/p5.TypedDict.js new file mode 100644 index 0000000000..e1d2f1fab2 --- /dev/null +++ b/src/data/p5.TypedDict.js @@ -0,0 +1,591 @@ +/** + * @module Data + * @submodule Dictionary + * @for p5.TypedDict + * @requires core + * + * This module defines the p5 methods for the p5 Dictionary classes + * these classes StringDict and NumberDict are for storing and working + * with key, value pairs + */ + +'use strict'; + +var p5 = require('../core/core'); + +/** + * + * Creates a new instance of p5.StringDict using the key, value pair + * or object you provide. + * + * @method createStringDict + * @param {String|Object} key or object + * @param {String} value + * @return {p5.StringDict} + * + */ + +p5.prototype.createStringDict = function() { + return new p5.StringDict(arguments[0], arguments[1]); +}; + +/** + * + * Creates a new instance of p5.NumberDict using the key, value pair + * or object you provide. + * + * @method createNumberDict + * @param {Number|Object} key or object + * @param {Number} value + * @return {p5.NumberDict} + * + */ + +p5.prototype.createNumberDict = function() { + return new p5.NumberDict(arguments[0], arguments[1]); +}; + +/** + * + * Base class for all p5.Dictionary types. More specifically + * typed Dictionary objects inherit from this + * + */ + +p5.TypedDict = function() { + this.data = {}; + if(arguments[0][0] instanceof Object) { + this.data = arguments[0][0]; + } else { + this.data[arguments[0][0]] = arguments[0][1]; + } + return this; +}; + +/** + * Returns the number of key-value pairs currently in Dictionary object + * + * @method size + * @return {Number} the number of key-value pairs in Dictionary object + * + * @example + *
+ * + * function setup() { + * var myDictionary = createNumberDict(1, 10); + * myDictionary.create(2, 20); + * myDictionary.create(3, 30); + * var amt = myDictionary.size(); // value of amt is 3 + * } + * + *
+ * + */ +p5.TypedDict.prototype.size = function(){ + return Object.keys(this.data).length; +}; + +/** + * Returns true if key exists in Dictionary + * otherwise returns false + * + * @method hasKey + * @param {Number|String} key that you want to access + * @return {Boolean} whether that key exists in Dictionary + * + * @example + *
+ * + * function setup() { + * var myDictionary = createStringDict('p5', 'js'); + * print(myDictionary.hasKey('p5')); // logs true to console + * } + * + *
+ * + */ + +p5.TypedDict.prototype.hasKey = function(key) { + return this.data.hasOwnProperty(key); +}; + +/** + * Returns value stored at supplied key. + * + * @method get + * @param {Number|String} key that you want to access + * @return {Number|String} the value stored at that key + * + * @example + *
+ * + * function setup() { + * var myDictionary = createStringDict('p5', 'js'); + * var myValue = myDictionary.get('p5'); + * print(myValue === 'js'); // logs true to console + * } + * + *
+ * + */ + +p5.TypedDict.prototype.get = function(key) { + if(this.data.hasOwnProperty(key)){ + return this.data[key]; + }else{ + console.log(key + ' does not exist in this Dictionary'); + } +}; + +/** + * Changes the value of key if in it already exists in + * in the Dictionary otherwise makes a new key-value pair + * + * @method set + * @param {Number|String} + * @param {Number|String} + * + * @example + *
+ * + * function setup() { + * var myDictionary = createStringDict('p5', 'js'); + * myDictionary.set('p5', 'JS'); + * myDictionary.print() + * // above logs "key: p5 - value: JS" to console + * } + * + *
+ * + */ + +p5.TypedDict.prototype.set = function(key, value) { + if (arguments.length === 2) { + if(!this.data.hasOwnProperty(key)){ + this.create(key, value); + } else { + this.data[key] = value; + } + } +}; + +/** + * private helper function to handle the user passing objects in + * during construction or calls to create() + */ + +p5.TypedDict.prototype._addObj = function(obj) { + for (var key in obj) { + if(this._validate(obj[key])) { + this.data[key] = obj[key]; + } else { + console.log('Those values dont work for this dictionary type.'); + } + } +}; + +/** + * Removes a key-value pair in the Dictionary + * + * @method create + * @param {Number|String|Object} + * @param {Number|String} + * + * @example + *
+ * + * function setup() { + * var myDictionary = createStringDict('p5', 'js'); + * myDictionary.create('happy', 'coding'); + * myDictionary.print() + * // above logs "key: p5 - value: js, key: happy - value: coding" to console + * } + * + *
+ * + */ + +p5.TypedDict.prototype.create = function() { + if(arguments.length === 1 && arguments[0] instanceof Object) { + this._addObj(arguments[0]); + } + else if(arguments.length === 2) { + var obj = {}; + obj[arguments[0]] = arguments[1]; + this._addObj(obj); + } else { + console.log('In order to create a new Dictionary entry you must pass ' + + 'an object or a key, value pair'); + } +}; + +/** + * Empties Dictionary of all key-value pairs + * @method clear + * + * + */ + +p5.TypedDict.prototype.clear = function(){ + for(var key in this.data) { + delete this.data[key]; + } +}; + +/** + * Removes a key-value pair in the Dictionary + * + * @method remove + * @param {Number|String} key for the pair to remove + * + * @example + *
+ * + * function setup() { + * var myDictionary = createStringDict('p5', 'js'); + * myDictionary.create('happy', 'coding'); + * myDictionary.print() + * // above logs "key: p5 - value: js, key: happy - value: coding" to console + * myDictionary.remove('p5'); + * myDictionary.print(); + * // above logs "key: happy value: coding" to console + * } + * + *
+ * + */ + +p5.TypedDict.prototype.remove = function(key) { + if(this.data.hasOwnProperty(key)) { + delete this.data[key]; + } else { + throw key + ' does not exist in this Dictionary'; + } +}; + +/** + * Logs the list of items currently in the Dictionary to the console + * + * @method print + */ + +p5.TypedDict.prototype.print = function() { + for (var item in this.data) { + console.log('key:' + item + ' value:' + this.data[item]); + } +}; + +/** + * Converts the Dictionary into a CSV file for local + * storage. + * + * @method saveTable + */ + +p5.TypedDict.prototype.saveTable = function() { + var output = ''; + + for (var key in this.data) { + output += key + ',' + this.data[key] + '\n'; + } + + var filename = arguments[0] || 'mycsv'; + var file = new Blob([output], {type: 'text/csv'}); + var href = window.URL.createObjectURL(file); + + p5.prototype.downloadFile(href, filename, 'csv'); +}; + +/** + * Converts the Dictionary into a JSON file for local + * storage. + * + * @method saveJSON + */ + +p5.TypedDict.prototype.saveJSON = function(filename, opt) { + p5.prototype.saveJSON(this.data, filename, opt); +}; + +/** + * private helper function to ensure that the user passed in valid + * values for the Dictionary type + */ + +p5.TypedDict.prototype._validate = function(key, value) { + return true; +}; + +/** + * + * A Dictionary class for Strings. + * + * + * @class p5.StringDict + * @constructor + * + */ + +p5.StringDict = function() { + p5.TypedDict.call(this, arguments); +}; + +p5.StringDict.prototype = Object.create(p5.TypedDict.prototype); + +p5.StringDict.prototype._validate = function(value) { + return (typeof value === 'string'); +}; + +/** + * + * A simple Dictionary class for Numbers. + * + * + * @class p5.NumberDict + * @constructor + * @extends p5.TypedDict + * + */ + +p5.NumberDict = function() { + p5.TypedDict.call(this, arguments); +}; + +p5.NumberDict.prototype = Object.create(p5.TypedDict.prototype); + +/** + * private helper function to ensure that the user passed in valid + * values for the Dictionary type + */ + +p5.NumberDict.prototype._validate = function(value) { + return (typeof value === 'number'); +}; + +/** + * Add to a value stored at a certain key + * The sum is stored in that location in the Dictionary. + * + * @method add + * @param {Number} Key for value you wish to add to + * @param {Number} Amount to add to the value + * @example + *
+ * function setup() { + * var myDictionary = createNumberDict(2, 5); + * myDictionary.add(2, 2); + * console.log(myDictionary.get(2)); // logs 7 to console. + * } + *
+ * + * + */ + +p5.NumberDict.prototype.add = function(key, amount) { + if(this.data.hasOwnProperty(key)){ + this.data[key] += amount; + } else { + console.log('The key - ' + key + ' does not exist in this dictionary.'); + } +}; + +/** + * Subtract from a value stored at a certain key + * The difference is stored in that location in the Dictionary. + * + * @method sub + * @param {Number} Key for value you wish to subtract from + * @param {Number} Amount to subtract from the value + * @example + *
+ * function setup() { + * var myDictionary = createNumberDict(2, 5); + * myDictionary.sub(2, 2); + * console.log(myDictionary.get(2)); // logs 3 to console. + * } + *
+ * + * + */ + +p5.NumberDict.prototype.sub = function(key, amount) { + this.add(key, -amount); +}; + +/** + * Multiply a value stored at a certain key + * The product is stored in that location in the Dictionary. + * + * @method mult + * @param {Number} Key for value you wish to multiply + * @param {Number} Amount to multiply the value by + * @example + *
+ * function setup() { + * var myDictionary = createNumberDict(2, 4); + * myDictionary.mult(2, 2); + * console.log(myDictionary.get(2)); // logs 8 to console. + * } + *
+ * + * + */ + +p5.NumberDict.prototype.mult = function(key, amount) { + if(this.data.hasOwnProperty(key)){ + this.data[key] *= amount; + } else { + console.log('The key - ' + key + ' does not exist in this dictionary.'); + } +}; + +/** + * Divide a value stored at a certain key + * The quotient is stored in that location in the Dictionary. + * + * @method div + * @param {Number} Key for value you wish to divide + * @param {Number} Amount to divide the value by + * @example + *
+ * function setup() { + * var myDictionary = createNumberDict(2, 8); + * myDictionary.div(2, 2); + * console.log(myDictionary.get(2)); // logs 4 to console. + * } + *
+ * + * + */ + +p5.NumberDict.prototype.div = function(key, amount) { + if(this.data.hasOwnProperty(key)){ + this.data[key] /= amount; + } else { + console.log('The key - ' + key + ' does not exist in this dictionary.'); + } +}; + +/** + * private helper function for finding lowest or highest value + * the argument 'flip' is used to flip the comparison arrow + * from 'less than' to 'greater than' + * + */ + +p5.NumberDict.prototype._valueTest = function(flip) { + if(Object.keys(this.data).length === 0) { + throw 'Unable to search for a minimum or maximum value on an empty NumberDict'; + } else if(Object.keys(this.data).length === 1) { + return this.data[Object.keys(this.data)[0]]; + } else { + var result = this.data[Object.keys(this.data)[0]]; + for(var key in this.data) { + if(this.data[key] * flip < result * flip) { + result = this.data[key]; + } + } + return result; + } +}; + +/** + * Return the lowest value. + * + * @method minValue + * @return {Number} + * @example + *
+ * function setup() { + * var myDictionary = createNumberDict({2 : -10, 4 : 0.65, 1.2 : 3}); + * var lowestValue = myDictionary.minValue(); // value is -10 + * } + *
+ * + */ + +p5.NumberDict.prototype.minValue = function() { + return this._valueTest(1); +}; + +/** + * Return the highest value. + * + * @method maxValue + * @return {Number} + * @example + *
+ * function setup() { + * var myDictionary = createNumberDict({2 : -10, 4 : 0.65, 1.2 : 3}); + * var highestValue = myDictionary.maxValue(); // value is 3 + * } + *
+ * + */ + +p5.NumberDict.prototype.maxValue = function() { + return this._valueTest(-1); +}; + +/** + * private helper function for finding lowest or highest key + * the argument 'flip' is used to flip the comparison arrow + * from 'less than' to 'greater than' + * + */ + +p5.NumberDict.prototype._keyTest = function(flip) { + if(Object.keys(this.data).length === 0) { + throw 'Unable to use minValue on an empty NumberDict'; + } else if(Object.keys(this.data).length === 1) { + return Object.keys(this.data)[0]; + } else { + var result = Object.keys(this.data)[0]; + for(var i=1; i + * function setup() { + * var myDictionary = createNumberDict({2 : 4, 4 : 6, 1.2 : 3}); + * var lowestKey = myDictionary.minKey(); // value is 1.2 + * } + * + * + */ + +p5.NumberDict.prototype.minKey = function() { + return this._keyTest(1); +}; + +/** + * Return the highest key. + * + * @method maxKey + * @return {Number} + * @example + *
+ * function setup() { + * var myDictionary = createNumberDict({ 2 : 4, 4 : 6, 1.2 : 3}); + * var highestKey = myDictionary.maxKey(); // value is 4 + * } + *
+ * + */ + +p5.NumberDict.prototype.maxKey = function() { + return this._keyTest(-1); +}; + +module.exports = p5.TypedDict; \ No newline at end of file diff --git a/test/test-minified.html b/test/test-minified.html index d434daf8c7..ae9cf3d6a4 100644 --- a/test/test-minified.html +++ b/test/test-minified.html @@ -44,6 +44,7 @@ + diff --git a/test/unit/data/dictionary.js b/test/unit/data/dictionary.js new file mode 100644 index 0000000000..919d13d97a --- /dev/null +++ b/test/unit/data/dictionary.js @@ -0,0 +1,104 @@ +suite('Dictionary Objects', function() { + var myp5 = new p5(function( p ) { + p.setup = function() {}; + p.draw = function() {}; + }); + + teardown(function(){ + myp5.clear(); + }); + + var stringDict = myp5.createStringDict('happy', 'coding'); + var numberDict = myp5.createNumberDict(1, 2); + + test('stringDict should be created', function() { + assert.isTrue(stringDict instanceof p5.StringDict); + }); + + test('numberDict should be created', function() { + assert.isTrue(numberDict instanceof p5.NumberDict); + }); + + test('check on the structure of stringDict', function() { + assert.deepEqual(JSON.stringify(stringDict), + JSON.stringify({data:{'happy':'coding'}})); + }); + + test('check on the structure of numberDict', function() { + assert.deepEqual(JSON.stringify(numberDict), + JSON.stringify({data:{1:2}})); + }); + + test('stringDict should have correct count', function() { + var amt = stringDict.size(); + assert.isTrue(amt === Object.keys(stringDict.data).length); + }); + + test('numberDict should have correct count', function() { + var amt = numberDict.size(); + assert.isTrue(amt === Object.keys(numberDict.data).length); + }); + + test('stringDict should add new key-value pairs and search', function() { + stringDict.create('fun', 'coding'); + assert.deepEqual(stringDict.get('fun'), 'coding'); + }); + + test('stringDict should add objects', function() { + stringDict.create({'p5': 'js', 'open': 'source'}); + assert.deepEqual(stringDict.get('open'), 'source'); + }); + + test('numberDict should add new key-value pairs and search', function() { + numberDict.create(3, 4); + assert.deepEqual(numberDict.get(3), 4); + }); + + test('stringDict should change existing values', function() { + stringDict.set('fun', 'times'); + assert.deepEqual(stringDict.get('fun'),'times'); + }); + + test('numberDict should change existing values', function() { + numberDict.set(1, 5); + assert.deepEqual(numberDict.get(1),5); + }); + + test('should clear stringDict', function() { + stringDict.clear(); + assert.deepEqual(stringDict, {data:{}}); + }); + + test('should add values together', function() { + numberDict.add(1, 4); + assert.deepEqual(numberDict.get(1), 9); + }); + + test('should subtract from value', function() { + numberDict.sub(1, 3); + assert.deepEqual(numberDict.get(1), 6); + }); + + test('should divide from value', function() { + numberDict.div(1, 3); + assert.deepEqual(numberDict.get(1), 2); + }); + + test('should multiply value', function() { + numberDict.mult(1, 3); + assert.deepEqual(numberDict.get(1), 6); + }); + + test('should find minimum value', function() { + assert.deepEqual(numberDict.minValue(), 4); + }); + + test('should find maximum value', function() { + assert.deepEqual(numberDict.maxValue(), 6); + }); + + test('should clear numberDict', function() { + numberDict.clear(); + assert.deepEqual(numberDict, {data:{}}); + }); +}); \ No newline at end of file