├── css ├── fancybox_overlay.png ├── fancybox_sprite.png ├── style.css └── jquery.fancybox.css ├── images ├── Spirograph-parameters.png └── startSpiro.svg ├── js ├── pym.min.js ├── script.js ├── jquery.fancybox.pack.js └── dat.gui.js └── index.html /css/fancybox_overlay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nbremer/spirograph/HEAD/css/fancybox_overlay.png -------------------------------------------------------------------------------- /css/fancybox_sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nbremer/spirograph/HEAD/css/fancybox_sprite.png -------------------------------------------------------------------------------- /images/Spirograph-parameters.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nbremer/spirograph/HEAD/images/Spirograph-parameters.png -------------------------------------------------------------------------------- /css/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: 'Open Sans', sans-serif; 3 | font-size: 12px; 4 | font-weight: 400; 5 | color: #E0E0E0; 6 | text-align: center; 7 | background: #101420; 8 | cursor: default; 9 | } 10 | 11 | a, a:hover { 12 | color: #EC0080; 13 | } 14 | 15 | li { 16 | text-align: left; 17 | font-size: 10px; 18 | } 19 | 20 | .dg, li.title, .close-button { 21 | font-family: 'Open Sans', sans-serif; 22 | font-weight: 300; 23 | font-size: 11px; 24 | text-shadow: none; 25 | } 26 | 27 | #info { 28 | font-family: 'Poiret One', cursive; 29 | font-size: 24px; 30 | position: absolute; 31 | bottom: 25px; 32 | left: 25px; 33 | opacity: 0.8; 34 | color: #F0F0F0; 35 | line-height: 24px; 36 | height: 24px; 37 | cursor: pointer; 38 | } 39 | 40 | #credits { 41 | font-family: 'Poiret One', cursive; 42 | font-size: 12px; 43 | position: absolute; 44 | bottom: 25px; 45 | right: 25px; 46 | opacity: 0.8; 47 | color: #D1D1D1; 48 | max-width: 70%; 49 | text-align: right; 50 | } 51 | 52 | .modal { 53 | color: #0A0A0A; 54 | } 55 | 56 | .modal-header { 57 | background: rgba(16,20,32,0.9); 58 | color: white; 59 | } 60 | 61 | .modal-content { 62 | background: rgba(252,252,255,0.8); 63 | border-radius: 0px; 64 | border: 0px; 65 | } 66 | 67 | .modal-title { 68 | font-family: 'Poiret One', cursive; 69 | font-size: 30px; 70 | font-weight: 700; 71 | } 72 | 73 | .modal-body{ 74 | text-align:left; 75 | color: #5E5E5E; 76 | } 77 | 78 | .start-message { 79 | font-size: 20px; 80 | text-align: center; 81 | font-family: 'Poiret One', cursive; 82 | font-weight: 700; 83 | padding-bottom: 15px; 84 | } 85 | 86 | .close, .close:hover { 87 | color: white; 88 | opacity: 1; 89 | text-shadow: none; 90 | } 91 | 92 | .spirograph-parameter-img { 93 | width: 90%; 94 | padding-top: 20px; 95 | padding-bottom: 20px; 96 | } 97 | 98 | .spirograph { 99 | fill: none; 100 | stroke-width: 1px; 101 | } -------------------------------------------------------------------------------- /js/pym.min.js: -------------------------------------------------------------------------------- 1 | /*! pym.js - v0.4.4 - 2015-07-16 */ 2 | !function(a){"function"==typeof define&&define.amd?define(a):"undefined"!=typeof module&&module.exports?module.exports=a():window.pym=a.call(this)}(function(){var a="xPYMx",b={},c=function(a){var b=new RegExp("[\\?&]"+a.replace(/[\[]/,"\\[").replace(/[\]]/,"\\]")+"=([^&#]*)"),c=b.exec(location.search);return null===c?"":decodeURIComponent(c[1].replace(/\+/g," "))},d=function(a,b){return"*"===b.xdomain||a.origin.match(new RegExp(b.xdomain+"$"))?!0:void 0},e=function(b,c,d){var e=["pym",b,c,d];return e.join(a)},f=function(b){var c=["pym",b,"(\\S+)","(.+)"];return new RegExp("^"+c.join(a)+"$")},g=function(){for(var a=document.querySelectorAll("[data-pym-src]:not([data-pym-auto-initialized])"),c=a.length,d=0;c>d;++d){var e=a[d];e.setAttribute("data-pym-auto-initialized",""),""===e.id&&(e.id="pym-"+d);var f=e.getAttribute("data-pym-src"),g=e.getAttribute("data-pym-xdomain"),h={};g&&(h.xdomain=g),new b.Parent(e.id,f,h)}};return b.Parent=function(a,b,c){this.id=a,this.url=b,this.el=document.getElementById(a),this.iframe=null,this.settings={xdomain:"*"},this.messageRegex=f(this.id),this.messageHandlers={},c=c||{},this._constructIframe=function(){var a=this.el.offsetWidth.toString();this.iframe=document.createElement("iframe");var b="",c=this.url.indexOf("#");c>-1&&(b=this.url.substring(c,this.url.length),this.url=this.url.substring(0,c)),this.url.indexOf("?")<0?this.url+="?":this.url+="&",this.iframe.src=this.url+"initialWidth="+a+"&childId="+this.id+"&parentUrl="+encodeURIComponent(window.location.href)+b,this.iframe.setAttribute("width","100%"),this.iframe.setAttribute("scrolling","no"),this.iframe.setAttribute("marginheight","0"),this.iframe.setAttribute("frameborder","0"),this.el.appendChild(this.iframe),window.addEventListener("resize",this._onResize)},this._onResize=function(){this.sendWidth()}.bind(this),this._fire=function(a,b){if(a in this.messageHandlers)for(var c=0;c 2 | 3 | 4 | 5 | Spirograph drawing 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 |
39 | 40 | 41 |
info
42 |
Created by Nadieh Bremer | VisualCinnamon.com
43 | 44 | 45 | 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /images/startSpiro.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | 17 | 18 | 21 | 22 | 25 | 28 | 31 | 34 | 37 | 40 | 41 | 44 | 45 | 48 | 49 | 50 | 51 | 54 | 55 | -------------------------------------------------------------------------------- /css/jquery.fancybox.css: -------------------------------------------------------------------------------- 1 | /*! fancyBox v2.1.5 fancyapps.com | fancyapps.com/fancybox/#license */ 2 | .fancybox-wrap, 3 | .fancybox-skin, 4 | .fancybox-outer, 5 | .fancybox-inner, 6 | .fancybox-image, 7 | .fancybox-wrap iframe, 8 | .fancybox-wrap object, 9 | .fancybox-nav, 10 | .fancybox-nav span, 11 | .fancybox-tmp 12 | { 13 | padding: 0; 14 | margin: 0; 15 | border: 0; 16 | outline: none; 17 | vertical-align: top; 18 | } 19 | 20 | .fancybox-wrap { 21 | position: absolute; 22 | top: 0; 23 | left: 0; 24 | z-index: 8020; 25 | } 26 | 27 | .fancybox-skin { 28 | position: relative; 29 | background: #f9f9f9; 30 | color: #444; 31 | text-shadow: none; 32 | -webkit-border-radius: 4px; 33 | -moz-border-radius: 4px; 34 | border-radius: 4px; 35 | } 36 | 37 | .fancybox-opened { 38 | z-index: 8030; 39 | } 40 | 41 | .fancybox-opened .fancybox-skin { 42 | -webkit-box-shadow: 0 10px 25px rgba(0, 0, 0, 0.5); 43 | -moz-box-shadow: 0 10px 25px rgba(0, 0, 0, 0.5); 44 | box-shadow: 0 10px 25px rgba(0, 0, 0, 0.5); 45 | } 46 | 47 | .fancybox-outer, .fancybox-inner { 48 | position: relative; 49 | } 50 | 51 | .fancybox-inner { 52 | overflow: hidden; 53 | } 54 | 55 | .fancybox-type-iframe .fancybox-inner { 56 | -webkit-overflow-scrolling: touch; 57 | } 58 | 59 | .fancybox-error { 60 | color: #444; 61 | font: 14px/20px "Helvetica Neue",Helvetica,Arial,sans-serif; 62 | margin: 0; 63 | padding: 15px; 64 | white-space: nowrap; 65 | } 66 | 67 | .fancybox-image, .fancybox-iframe { 68 | display: block; 69 | width: 100%; 70 | height: 100%; 71 | } 72 | 73 | .fancybox-image { 74 | max-width: 100%; 75 | max-height: 100%; 76 | } 77 | 78 | #fancybox-loading, .fancybox-close, .fancybox-prev span, .fancybox-next span { 79 | background-image: url('fancybox_sprite.png'); 80 | } 81 | 82 | #fancybox-loading { 83 | position: fixed; 84 | top: 50%; 85 | left: 50%; 86 | margin-top: -22px; 87 | margin-left: -22px; 88 | background-position: 0 -108px; 89 | opacity: 0.8; 90 | cursor: pointer; 91 | z-index: 8060; 92 | } 93 | 94 | #fancybox-loading div { 95 | width: 44px; 96 | height: 44px; 97 | background: url('fancybox_loading.gif') center center no-repeat; 98 | } 99 | 100 | .fancybox-close { 101 | position: absolute; 102 | top: -18px; 103 | right: -18px; 104 | width: 36px; 105 | height: 36px; 106 | cursor: pointer; 107 | z-index: 8040; 108 | } 109 | 110 | .fancybox-nav { 111 | position: absolute; 112 | top: 0; 113 | width: 40%; 114 | height: 100%; 115 | cursor: pointer; 116 | text-decoration: none; 117 | background: transparent url('blank.gif'); /* helps IE */ 118 | -webkit-tap-highlight-color: rgba(0,0,0,0); 119 | z-index: 8040; 120 | } 121 | 122 | .fancybox-prev { 123 | left: 0; 124 | } 125 | 126 | .fancybox-next { 127 | right: 0; 128 | } 129 | 130 | .fancybox-nav span { 131 | position: absolute; 132 | top: 50%; 133 | width: 36px; 134 | height: 34px; 135 | margin-top: -18px; 136 | cursor: pointer; 137 | z-index: 8040; 138 | visibility: hidden; 139 | } 140 | 141 | .fancybox-prev span { 142 | left: 10px; 143 | background-position: 0 -36px; 144 | } 145 | 146 | .fancybox-next span { 147 | right: 10px; 148 | background-position: 0 -72px; 149 | } 150 | 151 | .fancybox-nav:hover span { 152 | visibility: visible; 153 | } 154 | 155 | .fancybox-tmp { 156 | position: absolute; 157 | top: -99999px; 158 | left: -99999px; 159 | visibility: hidden; 160 | max-width: 99999px; 161 | max-height: 99999px; 162 | overflow: visible !important; 163 | } 164 | 165 | /* Overlay helper */ 166 | 167 | .fancybox-lock { 168 | overflow: hidden !important; 169 | width: auto; 170 | } 171 | 172 | .fancybox-lock body { 173 | overflow: hidden !important; 174 | } 175 | 176 | .fancybox-lock-test { 177 | overflow-y: hidden !important; 178 | } 179 | 180 | .fancybox-overlay { 181 | position: absolute; 182 | top: 0; 183 | left: 0; 184 | overflow: hidden; 185 | display: none; 186 | z-index: 8010; 187 | background: url('fancybox_overlay.png'); 188 | } 189 | 190 | .fancybox-overlay-fixed { 191 | position: fixed; 192 | bottom: 0; 193 | right: 0; 194 | } 195 | 196 | .fancybox-lock .fancybox-overlay { 197 | overflow: auto; 198 | overflow-y: scroll; 199 | } 200 | 201 | /* Title helper */ 202 | 203 | .fancybox-title { 204 | visibility: hidden; 205 | font: normal 13px/20px "Helvetica Neue",Helvetica,Arial,sans-serif; 206 | position: relative; 207 | text-shadow: none; 208 | z-index: 8050; 209 | } 210 | 211 | .fancybox-opened .fancybox-title { 212 | visibility: visible; 213 | } 214 | 215 | .fancybox-title-float-wrap { 216 | position: absolute; 217 | bottom: 0; 218 | right: 50%; 219 | margin-bottom: -35px; 220 | z-index: 8050; 221 | text-align: center; 222 | } 223 | 224 | .fancybox-title-float-wrap .child { 225 | display: inline-block; 226 | margin-right: -100%; 227 | padding: 2px 20px; 228 | background: transparent; /* Fallback for web browsers that doesn't support RGBa */ 229 | background: rgba(0, 0, 0, 0.8); 230 | -webkit-border-radius: 15px; 231 | -moz-border-radius: 15px; 232 | border-radius: 15px; 233 | text-shadow: 0 1px 2px #222; 234 | color: #FFF; 235 | font-weight: bold; 236 | line-height: 24px; 237 | white-space: nowrap; 238 | } 239 | 240 | .fancybox-title-outside-wrap { 241 | position: relative; 242 | margin-top: 10px; 243 | color: #fff; 244 | } 245 | 246 | .fancybox-title-inside-wrap { 247 | padding-top: 10px; 248 | } 249 | 250 | .fancybox-title-over-wrap { 251 | position: absolute; 252 | bottom: 0; 253 | left: 0; 254 | color: #fff; 255 | padding: 10px; 256 | background: #000; 257 | background: rgba(0, 0, 0, .8); 258 | } 259 | 260 | /*Retina graphics!*/ 261 | @media only screen and (-webkit-min-device-pixel-ratio: 1.5), 262 | only screen and (min--moz-device-pixel-ratio: 1.5), 263 | only screen and (min-device-pixel-ratio: 1.5){ 264 | 265 | #fancybox-loading, .fancybox-close, .fancybox-prev span, .fancybox-next span { 266 | background-image: url('fancybox_sprite@2x.png'); 267 | background-size: 44px 152px; /*The size of the normal image, half the size of the hi-res image*/ 268 | } 269 | 270 | #fancybox-loading div { 271 | background-image: url('fancybox_loading@2x.gif'); 272 | background-size: 24px 24px; /*The size of the normal image, half the size of the hi-res image*/ 273 | } 274 | } -------------------------------------------------------------------------------- /js/script.js: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////// 2 | //////////////////////// Set-up //////////////////////////// 3 | //////////////////////////////////////////////////////////// 4 | 5 | var screenWidth = $(window).innerWidth(), 6 | screenHeight = ( $(window).innerHeight() > 160 ? $(window).innerHeight() : screenWidth ); 7 | mobileScreen = (screenWidth > 500 ? false : true); 8 | 9 | var margin = {left: 0, top: 0, right: 0, bottom: 0}, 10 | width = screenWidth - margin.left - margin.right - 15, 11 | height = (mobileScreen ? 300 : screenHeight) - margin.top - margin.bottom - 25; 12 | 13 | //Create the SVG 14 | var svg = d3.select("#chart").append("svg") 15 | .attr("width", (width + margin.left + margin.right)) 16 | .attr("height", (height + margin.top + margin.bottom)) 17 | .style("isolation", "isolate") 18 | .append("g").attr("class", "wrapper") 19 | .attr("transform", "translate(" + (width / 2 + margin.left) + "," + (height / 2 + margin.top) + ")"); 20 | 21 | //If the viewer clicks anywhere and the screen then draw a spiro 22 | //Mainly meant for the mobile viewers 23 | d3.select("#chart").on("click", function() { 24 | addRandomSpiro(); //Boolean(Math.floor(Math.random() * 2)) ? addSpiro() : addDashedSpiro() 25 | }); 26 | 27 | //////////////////////////////////////////////////////////// 28 | ////////////// Spirograph initial settings ///////////////// 29 | //////////////////////////////////////////////////////////// 30 | 31 | var maxSize = 200, 32 | colorReset = true, 33 | colorCounter = 1, 34 | maxRadiusScreenFirstSpiro = Math.min(width, height) / 2 * 0.6; 35 | 36 | var colors = ["#EC0080", "#00AC93", "#FFE763"]; 37 | var numColors = colors.length; 38 | 39 | //Set initial random spirograph parameters 40 | var spiroParameters = {}; 41 | //Drawing controls 42 | spiroParameters["Add spiro"] = addSpiro; 43 | spiroParameters["Add dashed spiro"] = addDashedSpiro; 44 | spiroParameters["Add random spiro"] = addRandomSpiro; 45 | spiroParameters["Remove last"] = removeLastSpiro; 46 | spiroParameters["Reset"] = resetSpiro; 47 | //Parameter controls 48 | spiroParameters["Duration"] = 4; //in seconds 49 | spiroParameters["Outer wheel"] = 96; 50 | spiroParameters["Inner wheel"] = 56; 51 | spiroParameters["% inner wheel"] = 0.8; 52 | spiroParameters[eval('"\\u03B1"')] = 0.05; 53 | spiroParameters["Start"] = 0; 54 | spiroParameters["Steps"] = 10000; 55 | spiroParameters["Line width"] = 2; 56 | spiroParameters["Color mode"] = "screen"; 57 | spiroParameters["Color"] = "#EC0080"; 58 | spiroParameters["Background"] = "#101420"; 59 | //Make the first spirograph fit nicely in the screen 60 | spiroParameters["Scale"] = Math.round(maxRadiusScreenFirstSpiro / spiroParameters["Outer wheel"] * 10)/10; 61 | 62 | //Basic line function 63 | var line = d3.svg.line() 64 | .x(function(d) { return d.x; }) 65 | .y(function(d) { return d.y; }); 66 | 67 | //////////////////////////////////////////////////////////// 68 | ////////////////// Spirograph functions //////////////////// 69 | //////////////////////////////////////////////////////////// 70 | 71 | function drawSpiro(doDash) { 72 | 73 | var path = svg.append("path") 74 | .attr("class", "spirograph") 75 | .attr("d", line(plotSpiroGraph()) ) 76 | //.attr("transform", "scale(" + spiroParameters["Scale"] + ")") 77 | .style("mix-blend-mode", spiroParameters["Color mode"]) 78 | .style("stroke", spiroParameters["Color"]) 79 | .style("stroke-width", spiroParameters["Line width"]); 80 | 81 | var totalLength = path.node().getTotalLength(); 82 | 83 | if (doDash) { 84 | //Adjusted from http://stackoverflow.com/questions/24021971/animate-the-drawing-of-a-dashed-svg-line 85 | var dashing = getRandomNumber(2,10) + ", " + getRandomNumber(2,10); //Create random dash pattern 86 | console.log("Dash pattern is: " + dashing); 87 | //This returns the length of adding all of the numbers in dashing (the length of one pattern in essense) 88 | var dashLength = 89 | dashing 90 | .split(/[\s,]/) 91 | .map(function (a) { return parseFloat(a) || 0 }) 92 | .reduce(function (a, b) { return a + b }); 93 | 94 | var dashCount = Math.ceil( totalLength / dashLength ); //How many of these dash patterns will fit inside the entire path? 95 | var newDashes = new Array(dashCount).join( dashing + " " ); //Create array that holds the pattern so it will fill the path 96 | //Then add one more dash pattern, namely with a visible part of length 0 (so nothing) and a white part 97 | //that is the same length as the entire path 98 | var dashArray = newDashes + " 0, " + totalLength; 99 | } else { 100 | //For a solid looking line, create a dash pattern with a visible part and a white part 101 | //that are the same length as the entire path 102 | var dashArray = totalLength + " " + totalLength; 103 | } 104 | 105 | //Animate the path by offsetting the path so all you see is the white last bit of dashArray 106 | //and then setting this to 0 in a transition 107 | path 108 | .attr("stroke-dasharray", dashArray) 109 | .attr("stroke-dashoffset", totalLength) 110 | .transition().duration(spiroParameters["Duration"] * 1000).ease("linear") 111 | .attr("stroke-dashoffset", 0); 112 | 113 | }//function drawSpiro 114 | 115 | function plotSpiroGraph() { 116 | 117 | var R = spiroParameters["Scale"] * spiroParameters["Outer wheel"]; 118 | var r = spiroParameters["Scale"] * spiroParameters["Inner wheel"]; 119 | var rho = spiroParameters["% inner wheel"]; 120 | var alpha = spiroParameters[eval('"\\u03B1"')]; 121 | alpha = alpha * Math.PI / 180; 122 | var start = spiroParameters["Start"]; 123 | var steps = spiroParameters["Steps"]; 124 | 125 | var x0 = 5e5, 126 | y0 = 5e5; 127 | 128 | //Create the x and y coordinates for the spirograph and put these in a variable 129 | var lineData = []; 130 | for(var theta = start; theta < (start + steps); theta += 1){ 131 | var t = (Math.PI / 180) * theta ; 132 | var x = (R-r) * Math.cos(t + alpha) + rho * r * Math.cos( (R-r)/r * t - alpha ) ; 133 | var y = (R-r) * Math.sin(t + alpha) - rho * r * Math.sin( (R-r)/r * t - alpha) ; 134 | 135 | lineData.push({x: x, y: y}); 136 | 137 | //Break out of the loop when you reach the starting location again 138 | //no use to run over the same loop again 139 | if( Math.abs(x - x0) < 1e-1 && Math.abs(y - y0) < 1e-1 && theta > start + 100 ) { 140 | //console.log(theta); 141 | break; 142 | }//if 143 | 144 | //Set the start location 145 | if(theta === start) { 146 | x0 = x; 147 | y0 = y; 148 | }//if 149 | 150 | }//for theta 151 | 152 | //Output the variables of this spiro 153 | console.log("Spirograph parameters: Outer wheel: " + spiroParameters["Outer wheel"] + ", Inner wheel: " + spiroParameters["Inner wheel"] + 154 | ", % of inner wheel: " + Math.round(rho*1000)/1000 + ", alpha: " + alpha + ", start: " + start + ", steps: " + (theta-start) + 155 | ", color: " + spiroParameters["Color"] + ", scale: " + spiroParameters["Scale"] + ", line width: " + spiroParameters["Line width"] ); 156 | 157 | return lineData; 158 | }//function plotSpiroGraph 159 | 160 | //////////////////////////////////////////////////////////// 161 | //////////// Spirograph control functions ////////////////// 162 | //////////////////////////////////////////////////////////// 163 | 164 | //Take random numbers for the spirograph variables 165 | function doRandomValues() { 166 | spiroParameters["Outer wheel"] = getRandomNumber(60, maxSize); 167 | outerRadiusContr.updateDisplay(); 168 | 169 | spiroParameters["Inner wheel"] = getRandomNumber(30, (spiroParameters["Outer wheel"] * 0.99)); 170 | //Check that r < R 171 | changeOuterRadius(spiroParameters["Outer wheel"]); 172 | 173 | spiroParameters["% inner wheel"] = Math.min( Math.random() + 0.2, 1); 174 | rhoContr.updateDisplay(); 175 | }//function doRandomValues 176 | 177 | //Rotate to another color of no other color was picked 178 | function pickNewColor() { 179 | spiroParameters["Color"] = colors[colorCounter % numColors]; 180 | colorContr.updateDisplay(); 181 | colorCounter++; 182 | colorReset = true; 183 | }//pickNewColor 184 | 185 | //Add a normal solid line spirograph 186 | function addSpiro() { 187 | //Create and draw a spiro 188 | drawSpiro(false); 189 | //Pick a new color if no other color has been set 190 | if ( colorReset ) pickNewColor(); 191 | }//function addSpiro 192 | 193 | //Add a dashed line spirograph 194 | function addDashedSpiro() { 195 | //Create and draw a dashed spiro 196 | drawSpiro(true); 197 | //Pick a new color if no other color has been set 198 | if ( colorReset ) pickNewColor(); 199 | }//function addDashedSpiro 200 | 201 | //Add a normal solid line spirograph 202 | function addRandomSpiro() { 203 | //Take random values 204 | doRandomValues(); 205 | //Create and draw a spiro 206 | drawSpiro(false); 207 | //Pick a new color if no other color has been set 208 | if ( colorReset ) pickNewColor(); 209 | }//function addRandomSpiro 210 | 211 | //Remove only the last drawn spirograph 212 | function removeLastSpiro() { 213 | //Remove the last drawn spiro 214 | d3.selectAll(".spirograph").last().remove(); 215 | //Move the color one back 216 | colorCounter = colorCounter - 2; 217 | if ( colorReset ) pickNewColor(); 218 | }//function removeLastSpiro 219 | 220 | //Remove all spirographs 221 | function resetSpiro() { 222 | //Remove all spiros 223 | d3.selectAll(".spirograph").remove(); 224 | }//function resetSpiro 225 | 226 | //////////////////////////////////////////////////////////// 227 | ///////////////// Drawing control functions //////////////// 228 | //////////////////////////////////////////////////////////// 229 | 230 | // Create an instance, which also creates a UI pane 231 | //https://github.com/dataarts/dat.gui 232 | var gui = new dat.GUI(); 233 | 234 | //Create a folder with drawing buttons 235 | var folder1 = gui.addFolder('Drawing'); 236 | 237 | folder1.add(spiroParameters, "Add spiro"); 238 | folder1.add(spiroParameters, "Add dashed spiro"); 239 | folder1.add(spiroParameters, "Add random spiro"); 240 | folder1.add(spiroParameters, "Remove last"); 241 | folder1.add(spiroParameters, "Reset"); 242 | //Open the drawing options by default 243 | folder1.open(); 244 | 245 | //////////////////////////////////////////////////////////// 246 | /////////////// Parameter control functions //////////////// 247 | //////////////////////////////////////////////////////////// 248 | 249 | //Create a folder with spirograph setting parameters 250 | var folder2 = gui.addFolder('Parameters'); 251 | 252 | var outerRadiusContr = folder2.add(spiroParameters, "Outer wheel").min(1).max(maxSize).step(1); 253 | outerRadiusContr.onChange(function(newValue) { changeOuterRadius(newValue); }); 254 | function changeOuterRadius(newValue) { 255 | spiroParameters["Outer radius"] = newValue; 256 | 257 | //Set the maximum of the inner wheel to the maximum of the outer fixed one 258 | innerRadiusContr.max(spiroParameters["Outer wheel"]); 259 | 260 | //Make sure that you can never have r > R 261 | if ( spiroParameters["Outer wheel"] <= spiroParameters["Inner wheel"] ) { 262 | spiroParameters["Inner wheel"] = spiroParameters["Outer wheel"]; 263 | }//if 264 | 265 | innerRadiusContr.updateDisplay(); 266 | }//function changeOuterRadius 267 | 268 | var innerRadiusContr = folder2.add(spiroParameters, "Inner wheel").min(1).max(spiroParameters["Outer wheel"]).step(1); 269 | innerRadiusContr.onChange(function(newValue) { spiroParameters["Inner wheel"] = newValue; }); 270 | 271 | var rhoContr = folder2.add(spiroParameters, "% inner wheel").min(0).max(1).step(0.05); 272 | rhoContr.onChange(function(newValue) { spiroParameters["% inner wheel"] = newValue; }); 273 | 274 | var alphaContr = folder2.add(spiroParameters, eval('"\\u03B1"')).min(-180).max(180).step(0.05); 275 | alphaContr.onChange(function(newValue) { spiroParameters[eval('"\\u03B1"')] = newValue; }); 276 | 277 | var startContr = folder2.add(spiroParameters, "Start").min(0).max(20e3).step(50); 278 | startContr.onChange(function(newValue) { spiroParameters["Start"] = newValue; }); 279 | 280 | var stepContr = folder2.add(spiroParameters, "Steps").min(0).max(20e3).step(50); 281 | stepContr.onChange(function(newValue) { spiroParameters["Steps"] = newValue; }); 282 | 283 | var scaleContr = folder2.add(spiroParameters, "Scale").min(0.1).max(5).step(0.1); 284 | scaleContr.onChange(function(newValue) { spiroParameters["Scale"] = newValue; }); 285 | 286 | var durationContr = folder2.add(spiroParameters, "Duration").min(1).max(20).step(1); 287 | durationContr.onChange(function(newValue) { spiroParameters["Duration"] = newValue; }); 288 | 289 | var widthContr = folder2.add(spiroParameters, "Line width").min(0).max(30).step(0.5); 290 | widthContr.onChange(function(newValue) { spiroParameters["Line width"] = newValue; }); 291 | 292 | var blendContr = folder2.add(spiroParameters, "Color mode", { Screen: "screen", Multiply: "multiply", None: "none" } ); 293 | blendContr.onChange(function(newValue) { spiroParameters["Color mode"] = newValue; }); 294 | 295 | var colorContr = folder2.addColor(spiroParameters, "Color"); 296 | colorContr.onChange(function(newValue) { 297 | spiroParameters["Color"] = newValue; 298 | colorReset = false; 299 | }); 300 | 301 | var backColorContr = folder2.addColor(spiroParameters, "Background"); 302 | backColorContr.onChange(function(newValue) { 303 | spiroParameters["Background"] = newValue; 304 | d3.select("body").style("background", newValue); 305 | }); 306 | 307 | //Open the parameter options by default 308 | folder2.open(); 309 | 310 | 311 | //Close the controls if the person is on mobile 312 | if(mobileScreen) gui.close(); 313 | 314 | //////////////////////////////////////////////////////////// 315 | ///////////////// Spiro icon below info text /////////////// 316 | //////////////////////////////////////////////////////////// 317 | 318 | //Only draw the extra spiro icon around the info box when not viewed on mobile 319 | if(!mobileScreen) { 320 | //Draw a spiro icon in the center 321 | var ImageWidth = 300; 322 | var infoImage = svg.append("svg:image") 323 | .attr("class", "infoImage") 324 | .attr("xlink:href", "images/startSpiro.svg") 325 | .attr("x", -width/2 - 45) 326 | .attr("y", height/2 - 110) 327 | .attr("width", 160) 328 | .attr("height", 160) 329 | .style("text-anchor", "middle"); 330 | }//if 331 | 332 | //Set the click functionality of the info button 333 | d3.select("#info").on("click", function() { 334 | //Open the modal 335 | $('#myModal').modal('show'); 336 | }); 337 | 338 | //////////////////////////////////////////////////////////// 339 | ////////////////////////// Start /////////////////////////// 340 | //////////////////////////////////////////////////////////// 341 | 342 | $(document).ready(function() { 343 | //Create the image pop-up functionality in the info box 344 | $("#spiro-parameters").fancybox({ 345 | helpers: { 346 | title : { type : 'float' } 347 | } 348 | }); 349 | 350 | //Start drawing a spirograph combination 1 second after reload 351 | setTimeout(function() { 352 | 353 | //var colors = ["#170E5E", "#2A85C8", "#88C425", "#2A85C8", "#3ac0de"], 354 | var colors = ["#170E5E", "#2A85C8", "#88C425", "#2A85C8", "#00AC93"], 355 | background = spiroParameters["Background"], 356 | thick = 3, 357 | step = 0.05, 358 | offset = 3.75, 359 | startRho = 0.8, 360 | steps = 2600, 361 | scale = Math.round(maxRadiusScreenFirstSpiro / 96 * 10)/10; 362 | 363 | //R, r, rho, alpha, start, steps, duration, width, blendmode, color, background, scale 364 | changeParameters(96, 56, startRho, 0, 0, steps, 4, thick, "screen", colors[0], background, scale, false); 365 | drawSpiro(false); 366 | for(var i = 1; i <=3; i++) { 367 | changeParameters(96, 56, startRho - i*step, i*offset, 0, steps, 4, thick, "screen", colors[i], background, scale, false); 368 | drawSpiro(false); 369 | 370 | changeParameters(96, 56, startRho - i*step, -i*offset, 0, steps, 4, thick, "screen", colors[i], background, scale, false); 371 | drawSpiro(false); 372 | }//for i 373 | changeParameters(96, 56, startRho - 4*step, 4*offset, 0, steps, 4, thick, "screen", colors[colors.length-1], background, scale, true); 374 | drawSpiro(false); 375 | 376 | }, 1000); 377 | }); 378 | 379 | //Change all the parameters at once 380 | function changeParameters(R, r, rho, alpha, start, steps, duration, width, blendmode, color, background, scale, update) { 381 | if(typeof R !== "undefined") spiroParameters["Outer wheel"] = R; 382 | if(typeof r !== "undefined") spiroParameters["Inner wheel"] = r; 383 | if(typeof rho !== "undefined") spiroParameters["% inner wheel"] = rho; 384 | if(typeof alpha !== "undefined") spiroParameters[eval('"\\u03B1"')] = alpha; 385 | if(typeof start !== "undefined") spiroParameters["Start"] = start; 386 | if(typeof steps !== "undefined") spiroParameters["Steps"] = steps; 387 | if(typeof duration !== "undefined") spiroParameters["Duration"] = duration; 388 | if(typeof width !== "undefined") spiroParameters["Line width"] = width; 389 | if(typeof blendmode !== "undefined") spiroParameters["Color mode"] = blendmode; 390 | if(typeof color !== "undefined") spiroParameters["Color"] = color; 391 | if(typeof background !== "undefined") spiroParameters["Background"] = background; 392 | if(typeof scale !== "undefined") spiroParameters["Scale"] = scale; 393 | 394 | if(update) { 395 | //Update the control box with the new parameters 396 | for (var i in folder2.__controllers) { 397 | folder2.__controllers[i].updateDisplay(); 398 | }//for i 399 | }//if 400 | }//changeParameters 401 | 402 | //////////////////////////////////////////////////////////// 403 | //////////////////// Helper Functions ////////////////////// 404 | //////////////////////////////////////////////////////////// 405 | 406 | //Add a property to D3 to select the first/last spiro drawn 407 | //From http://stackoverflow.com/questions/25405359/how-can-i-select-last-child-in-d3-js 408 | d3.selection.prototype.first = function() { 409 | return d3.select(this[0][0]); 410 | }; 411 | d3.selection.prototype.last = function() { 412 | var last = this.size() - 1; 413 | return d3.select(this[0][last]); 414 | }; 415 | 416 | function getRandomNumber(start, end) { 417 | return (Math.floor((Math.random() * (end-start))) + start); 418 | }//function getRandomNumber 419 | -------------------------------------------------------------------------------- /js/jquery.fancybox.pack.js: -------------------------------------------------------------------------------- 1 | /*! fancyBox v2.1.5 fancyapps.com | fancyapps.com/fancybox/#license */ 2 | (function(r,G,f,v){var J=f("html"),n=f(r),p=f(G),b=f.fancybox=function(){b.open.apply(this,arguments)},I=navigator.userAgent.match(/msie/i),B=null,s=G.createTouch!==v,t=function(a){return a&&a.hasOwnProperty&&a instanceof f},q=function(a){return a&&"string"===f.type(a)},E=function(a){return q(a)&&0
',image:'',iframe:'",error:'

The requested content cannot be loaded.
Please try again later.

',closeBtn:'',next:'',prev:''},openEffect:"fade",openSpeed:250,openEasing:"swing",openOpacity:!0, 6 | openMethod:"zoomIn",closeEffect:"fade",closeSpeed:250,closeEasing:"swing",closeOpacity:!0,closeMethod:"zoomOut",nextEffect:"elastic",nextSpeed:250,nextEasing:"swing",nextMethod:"changeIn",prevEffect:"elastic",prevSpeed:250,prevEasing:"swing",prevMethod:"changeOut",helpers:{overlay:!0,title:!0},onCancel:f.noop,beforeLoad:f.noop,afterLoad:f.noop,beforeShow:f.noop,afterShow:f.noop,beforeChange:f.noop,beforeClose:f.noop,afterClose:f.noop},group:{},opts:{},previous:null,coming:null,current:null,isActive:!1, 7 | isOpen:!1,isOpened:!1,wrap:null,skin:null,outer:null,inner:null,player:{timer:null,isActive:!1},ajaxLoad:null,imgPreload:null,transitions:{},helpers:{},open:function(a,d){if(a&&(f.isPlainObject(d)||(d={}),!1!==b.close(!0)))return f.isArray(a)||(a=t(a)?f(a).get():[a]),f.each(a,function(e,c){var k={},g,h,j,m,l;"object"===f.type(c)&&(c.nodeType&&(c=f(c)),t(c)?(k={href:c.data("fancybox-href")||c.attr("href"),title:c.data("fancybox-title")||c.attr("title"),isDom:!0,element:c},f.metadata&&f.extend(!0,k, 8 | c.metadata())):k=c);g=d.href||k.href||(q(c)?c:null);h=d.title!==v?d.title:k.title||"";m=(j=d.content||k.content)?"html":d.type||k.type;!m&&k.isDom&&(m=c.data("fancybox-type"),m||(m=(m=c.prop("class").match(/fancybox\.(\w+)/))?m[1]:null));q(g)&&(m||(b.isImage(g)?m="image":b.isSWF(g)?m="swf":"#"===g.charAt(0)?m="inline":q(c)&&(m="html",j=c)),"ajax"===m&&(l=g.split(/\s+/,2),g=l.shift(),l=l.shift()));j||("inline"===m?g?j=f(q(g)?g.replace(/.*(?=#[^\s]+$)/,""):g):k.isDom&&(j=c):"html"===m?j=g:!m&&(!g&& 9 | k.isDom)&&(m="inline",j=c));f.extend(k,{href:g,type:m,content:j,title:h,selector:l});a[e]=k}),b.opts=f.extend(!0,{},b.defaults,d),d.keys!==v&&(b.opts.keys=d.keys?f.extend({},b.defaults.keys,d.keys):!1),b.group=a,b._start(b.opts.index)},cancel:function(){var a=b.coming;a&&!1!==b.trigger("onCancel")&&(b.hideLoading(),b.ajaxLoad&&b.ajaxLoad.abort(),b.ajaxLoad=null,b.imgPreload&&(b.imgPreload.onload=b.imgPreload.onerror=null),a.wrap&&a.wrap.stop(!0,!0).trigger("onReset").remove(),b.coming=null,b.current|| 10 | b._afterZoomOut(a))},close:function(a){b.cancel();!1!==b.trigger("beforeClose")&&(b.unbindEvents(),b.isActive&&(!b.isOpen||!0===a?(f(".fancybox-wrap").stop(!0).trigger("onReset").remove(),b._afterZoomOut()):(b.isOpen=b.isOpened=!1,b.isClosing=!0,f(".fancybox-item, .fancybox-nav").remove(),b.wrap.stop(!0,!0).removeClass("fancybox-opened"),b.transitions[b.current.closeMethod]())))},play:function(a){var d=function(){clearTimeout(b.player.timer)},e=function(){d();b.current&&b.player.isActive&&(b.player.timer= 11 | setTimeout(b.next,b.current.playSpeed))},c=function(){d();p.unbind(".player");b.player.isActive=!1;b.trigger("onPlayEnd")};if(!0===a||!b.player.isActive&&!1!==a){if(b.current&&(b.current.loop||b.current.index=c.index?"next":"prev"],b.router=e||"jumpto",c.loop&&(0>a&&(a=c.group.length+a%c.group.length),a%=c.group.length),c.group[a]!==v&&(b.cancel(),b._start(a)))},reposition:function(a,d){var e=b.current,c=e?e.wrap:null,k;c&&(k=b._getPosition(d),a&&"scroll"===a.type?(delete k.position,c.stop(!0,!0).animate(k,200)):(c.css(k),e.pos=f.extend({},e.dim,k)))},update:function(a){var d= 13 | a&&a.type,e=!d||"orientationchange"===d;e&&(clearTimeout(B),B=null);b.isOpen&&!B&&(B=setTimeout(function(){var c=b.current;c&&!b.isClosing&&(b.wrap.removeClass("fancybox-tmp"),(e||"load"===d||"resize"===d&&c.autoResize)&&b._setDimension(),"scroll"===d&&c.canShrink||b.reposition(a),b.trigger("onUpdate"),B=null)},e&&!s?0:300))},toggle:function(a){b.isOpen&&(b.current.fitToView="boolean"===f.type(a)?a:!b.current.fitToView,s&&(b.wrap.removeAttr("style").addClass("fancybox-tmp"),b.trigger("onUpdate")), 14 | b.update())},hideLoading:function(){p.unbind(".loading");f("#fancybox-loading").remove()},showLoading:function(){var a,d;b.hideLoading();a=f('
').click(b.cancel).appendTo("body");p.bind("keydown.loading",function(a){if(27===(a.which||a.keyCode))a.preventDefault(),b.cancel()});b.defaults.fixed||(d=b.getViewport(),a.css({position:"absolute",top:0.5*d.h+d.y,left:0.5*d.w+d.x}))},getViewport:function(){var a=b.current&&b.current.locked||!1,d={x:n.scrollLeft(), 15 | y:n.scrollTop()};a?(d.w=a[0].clientWidth,d.h=a[0].clientHeight):(d.w=s&&r.innerWidth?r.innerWidth:n.width(),d.h=s&&r.innerHeight?r.innerHeight:n.height());return d},unbindEvents:function(){b.wrap&&t(b.wrap)&&b.wrap.unbind(".fb");p.unbind(".fb");n.unbind(".fb")},bindEvents:function(){var a=b.current,d;a&&(n.bind("orientationchange.fb"+(s?"":" resize.fb")+(a.autoCenter&&!a.locked?" scroll.fb":""),b.update),(d=a.keys)&&p.bind("keydown.fb",function(e){var c=e.which||e.keyCode,k=e.target||e.srcElement; 16 | if(27===c&&b.coming)return!1;!e.ctrlKey&&(!e.altKey&&!e.shiftKey&&!e.metaKey&&(!k||!k.type&&!f(k).is("[contenteditable]")))&&f.each(d,function(d,k){if(1h[0].clientWidth||h[0].clientHeight&&h[0].scrollHeight>h[0].clientHeight),h=f(h).parent();if(0!==c&&!j&&1g||0>k)b.next(0>g?"up":"right");d.preventDefault()}}))},trigger:function(a,d){var e,c=d||b.coming||b.current;if(c){f.isFunction(c[a])&&(e=c[a].apply(c,Array.prototype.slice.call(arguments,1)));if(!1===e)return!1;c.helpers&&f.each(c.helpers,function(d,e){if(e&&b.helpers[d]&&f.isFunction(b.helpers[d][a]))b.helpers[d][a](f.extend(!0, 18 | {},b.helpers[d].defaults,e),c)});p.trigger(a)}},isImage:function(a){return q(a)&&a.match(/(^data:image\/.*,)|(\.(jp(e|g|eg)|gif|png|bmp|webp|svg)((\?|#).*)?$)/i)},isSWF:function(a){return q(a)&&a.match(/\.(swf)((\?|#).*)?$/i)},_start:function(a){var d={},e,c;a=l(a);e=b.group[a]||null;if(!e)return!1;d=f.extend(!0,{},b.opts,e);e=d.margin;c=d.padding;"number"===f.type(e)&&(d.margin=[e,e,e,e]);"number"===f.type(c)&&(d.padding=[c,c,c,c]);d.modal&&f.extend(!0,d,{closeBtn:!1,closeClick:!1,nextClick:!1,arrows:!1, 19 | mouseWheel:!1,keys:null,helpers:{overlay:{closeClick:!1}}});d.autoSize&&(d.autoWidth=d.autoHeight=!0);"auto"===d.width&&(d.autoWidth=!0);"auto"===d.height&&(d.autoHeight=!0);d.group=b.group;d.index=a;b.coming=d;if(!1===b.trigger("beforeLoad"))b.coming=null;else{c=d.type;e=d.href;if(!c)return b.coming=null,b.current&&b.router&&"jumpto"!==b.router?(b.current.index=a,b[b.router](b.direction)):!1;b.isActive=!0;if("image"===c||"swf"===c)d.autoHeight=d.autoWidth=!1,d.scrolling="visible";"image"===c&&(d.aspectRatio= 20 | !0);"iframe"===c&&s&&(d.scrolling="scroll");d.wrap=f(d.tpl.wrap).addClass("fancybox-"+(s?"mobile":"desktop")+" fancybox-type-"+c+" fancybox-tmp "+d.wrapCSS).appendTo(d.parent||"body");f.extend(d,{skin:f(".fancybox-skin",d.wrap),outer:f(".fancybox-outer",d.wrap),inner:f(".fancybox-inner",d.wrap)});f.each(["Top","Right","Bottom","Left"],function(a,b){d.skin.css("padding"+b,w(d.padding[a]))});b.trigger("onReady");if("inline"===c||"html"===c){if(!d.content||!d.content.length)return b._error("content")}else if(!e)return b._error("href"); 21 | "image"===c?b._loadImage():"ajax"===c?b._loadAjax():"iframe"===c?b._loadIframe():b._afterLoad()}},_error:function(a){f.extend(b.coming,{type:"html",autoWidth:!0,autoHeight:!0,minWidth:0,minHeight:0,scrolling:"no",hasError:a,content:b.coming.tpl.error});b._afterLoad()},_loadImage:function(){var a=b.imgPreload=new Image;a.onload=function(){this.onload=this.onerror=null;b.coming.width=this.width/b.opts.pixelRatio;b.coming.height=this.height/b.opts.pixelRatio;b._afterLoad()};a.onerror=function(){this.onload= 22 | this.onerror=null;b._error("image")};a.src=b.coming.href;!0!==a.complete&&b.showLoading()},_loadAjax:function(){var a=b.coming;b.showLoading();b.ajaxLoad=f.ajax(f.extend({},a.ajax,{url:a.href,error:function(a,e){b.coming&&"abort"!==e?b._error("ajax",a):b.hideLoading()},success:function(d,e){"success"===e&&(a.content=d,b._afterLoad())}}))},_loadIframe:function(){var a=b.coming,d=f(a.tpl.iframe.replace(/\{rnd\}/g,(new Date).getTime())).attr("scrolling",s?"auto":a.iframe.scrolling).attr("src",a.href); 23 | f(a.wrap).bind("onReset",function(){try{f(this).find("iframe").hide().attr("src","//about:blank").end().empty()}catch(a){}});a.iframe.preload&&(b.showLoading(),d.one("load",function(){f(this).data("ready",1);s||f(this).bind("load.fb",b.update);f(this).parents(".fancybox-wrap").width("100%").removeClass("fancybox-tmp").show();b._afterLoad()}));a.content=d.appendTo(a.inner);a.iframe.preload||b._afterLoad()},_preloadImages:function(){var a=b.group,d=b.current,e=a.length,c=d.preload?Math.min(d.preload, 24 | e-1):0,f,g;for(g=1;g<=c;g+=1)f=a[(d.index+g)%e],"image"===f.type&&f.href&&((new Image).src=f.href)},_afterLoad:function(){var a=b.coming,d=b.current,e,c,k,g,h;b.hideLoading();if(a&&!1!==b.isActive)if(!1===b.trigger("afterLoad",a,d))a.wrap.stop(!0).trigger("onReset").remove(),b.coming=null;else{d&&(b.trigger("beforeChange",d),d.wrap.stop(!0).removeClass("fancybox-opened").find(".fancybox-item, .fancybox-nav").remove());b.unbindEvents();e=a.content;c=a.type;k=a.scrolling;f.extend(b,{wrap:a.wrap,skin:a.skin, 25 | outer:a.outer,inner:a.inner,current:a,previous:d});g=a.href;switch(c){case "inline":case "ajax":case "html":a.selector?e=f("
").html(e).find(a.selector):t(e)&&(e.data("fancybox-placeholder")||e.data("fancybox-placeholder",f('
').insertAfter(e).hide()),e=e.show().detach(),a.wrap.bind("onReset",function(){f(this).find(e).length&&e.hide().replaceAll(e.data("fancybox-placeholder")).data("fancybox-placeholder",!1)}));break;case "image":e=a.tpl.image.replace("{href}", 26 | g);break;case "swf":e='',h="",f.each(a.swf,function(a,b){e+='';h+=" "+a+'="'+b+'"'}),e+='"}(!t(e)||!e.parent().is(a.inner))&&a.inner.append(e);b.trigger("beforeShow");a.inner.css("overflow","yes"===k?"scroll": 27 | "no"===k?"hidden":k);b._setDimension();b.reposition();b.isOpen=!1;b.coming=null;b.bindEvents();if(b.isOpened){if(d.prevMethod)b.transitions[d.prevMethod]()}else f(".fancybox-wrap").not(a.wrap).stop(!0).trigger("onReset").remove();b.transitions[b.isOpened?a.nextMethod:a.openMethod]();b._preloadImages()}},_setDimension:function(){var a=b.getViewport(),d=0,e=!1,c=!1,e=b.wrap,k=b.skin,g=b.inner,h=b.current,c=h.width,j=h.height,m=h.minWidth,u=h.minHeight,n=h.maxWidth,p=h.maxHeight,s=h.scrolling,q=h.scrollOutside? 28 | h.scrollbarWidth:0,x=h.margin,y=l(x[1]+x[3]),r=l(x[0]+x[2]),v,z,t,C,A,F,B,D,H;e.add(k).add(g).width("auto").height("auto").removeClass("fancybox-tmp");x=l(k.outerWidth(!0)-k.width());v=l(k.outerHeight(!0)-k.height());z=y+x;t=r+v;C=E(c)?(a.w-z)*l(c)/100:c;A=E(j)?(a.h-t)*l(j)/100:j;if("iframe"===h.type){if(H=h.content,h.autoHeight&&1===H.data("ready"))try{H[0].contentWindow.document.location&&(g.width(C).height(9999),F=H.contents().find("body"),q&&F.css("overflow-x","hidden"),A=F.outerHeight(!0))}catch(G){}}else if(h.autoWidth|| 29 | h.autoHeight)g.addClass("fancybox-tmp"),h.autoWidth||g.width(C),h.autoHeight||g.height(A),h.autoWidth&&(C=g.width()),h.autoHeight&&(A=g.height()),g.removeClass("fancybox-tmp");c=l(C);j=l(A);D=C/A;m=l(E(m)?l(m,"w")-z:m);n=l(E(n)?l(n,"w")-z:n);u=l(E(u)?l(u,"h")-t:u);p=l(E(p)?l(p,"h")-t:p);F=n;B=p;h.fitToView&&(n=Math.min(a.w-z,n),p=Math.min(a.h-t,p));z=a.w-y;r=a.h-r;h.aspectRatio?(c>n&&(c=n,j=l(c/D)),j>p&&(j=p,c=l(j*D)),cz||y>r)&&(c>m&&j>u)&&!(19n&&(c=n,j=l(c/D)),g.width(c).height(j),e.width(c+x),a=e.width(),y=e.height();else c=Math.max(m,Math.min(c,c-(a-z))),j=Math.max(u,Math.min(j,j-(y-r)));q&&("auto"===s&&jz||y>r)&&c>m&&j>u;c=h.aspectRatio?cu&&j
').appendTo(b.coming?b.coming.parent:a.parent);this.fixed=!1;a.fixed&&b.defaults.fixed&&(this.overlay.addClass("fancybox-overlay-fixed"),this.fixed=!0)},open:function(a){var d=this;a=f.extend({},this.defaults,a);this.overlay?this.overlay.unbind(".overlay").width("auto").height("auto"):this.create(a);this.fixed||(n.bind("resize.overlay",f.proxy(this.update,this)),this.update());a.closeClick&&this.overlay.bind("click.overlay",function(a){if(f(a.target).hasClass("fancybox-overlay"))return b.isActive? 40 | b.close():d.close(),!1});this.overlay.css(a.css).show()},close:function(){var a,b;n.unbind("resize.overlay");this.el.hasClass("fancybox-lock")&&(f(".fancybox-margin").removeClass("fancybox-margin"),a=n.scrollTop(),b=n.scrollLeft(),this.el.removeClass("fancybox-lock"),n.scrollTop(a).scrollLeft(b));f(".fancybox-overlay").remove().hide();f.extend(this,{overlay:null,fixed:!1})},update:function(){var a="100%",b;this.overlay.width(a).height("100%");I?(b=Math.max(G.documentElement.offsetWidth,G.body.offsetWidth), 41 | p.width()>b&&(a=p.width())):p.width()>n.width()&&(a=p.width());this.overlay.width(a).height(p.height())},onReady:function(a,b){var e=this.overlay;f(".fancybox-overlay").stop(!0,!0);e||this.create(a);a.locked&&(this.fixed&&b.fixed)&&(e||(this.margin=p.height()>n.height()?f("html").css("margin-right").replace("px",""):!1),b.locked=this.overlay.append(b.wrap),b.fixed=!1);!0===a.showEarly&&this.beforeShow.apply(this,arguments)},beforeShow:function(a,b){var e,c;b.locked&&(!1!==this.margin&&(f("*").filter(function(){return"fixed"=== 42 | f(this).css("position")&&!f(this).hasClass("fancybox-overlay")&&!f(this).hasClass("fancybox-wrap")}).addClass("fancybox-margin"),this.el.addClass("fancybox-margin")),e=n.scrollTop(),c=n.scrollLeft(),this.el.addClass("fancybox-lock"),n.scrollTop(e).scrollLeft(c));this.open(a)},onUpdate:function(){this.fixed||this.update()},afterClose:function(a){this.overlay&&!b.coming&&this.overlay.fadeOut(a.speedOut,f.proxy(this.close,this))}};b.helpers.title={defaults:{type:"float",position:"bottom"},beforeShow:function(a){var d= 43 | b.current,e=d.title,c=a.type;f.isFunction(e)&&(e=e.call(d.element,d));if(q(e)&&""!==f.trim(e)){d=f('
'+e+"
");switch(c){case "inside":c=b.skin;break;case "outside":c=b.wrap;break;case "over":c=b.inner;break;default:c=b.skin,d.appendTo("body"),I&&d.width(d.width()),d.wrapInner(''),b.current.margin[2]+=Math.abs(l(d.css("margin-bottom")))}d["top"===a.position?"prependTo":"appendTo"](c)}}};f.fn.fancybox=function(a){var d, 44 | e=f(this),c=this.selector||"",k=function(g){var h=f(this).blur(),j=d,k,l;!g.ctrlKey&&(!g.altKey&&!g.shiftKey&&!g.metaKey)&&!h.is(".fancybox-wrap")&&(k=a.groupAttr||"data-fancybox-group",l=h.attr(k),l||(k="rel",l=h.get(0)[k]),l&&(""!==l&&"nofollow"!==l)&&(h=c.length?f(c):e,h=h.filter("["+k+'="'+l+'"]'),j=h.index(this)),a.index=j,!1!==b.open(h,a)&&g.preventDefault())};a=a||{};d=a.index||0;!c||!1===a.live?e.unbind("click.fb-start").bind("click.fb-start",k):p.undelegate(c,"click.fb-start").delegate(c+ 45 | ":not('.fancybox-item, .fancybox-nav')","click.fb-start",k);this.filter("[data-fancybox-start=1]").trigger("click");return this};p.ready(function(){var a,d;f.scrollbarWidth===v&&(f.scrollbarWidth=function(){var a=f('
').appendTo("body"),b=a.children(),b=b.innerWidth()-b.height(99).innerWidth();a.remove();return b});if(f.support.fixedPosition===v){a=f.support;d=f('
').appendTo("body");var e=20=== 46 | d[0].offsetTop||15===d[0].offsetTop;d.remove();a.fixedPosition=e}f.extend(b.defaults,{scrollbarWidth:f.scrollbarWidth(),fixed:f.support.fixedPosition,parent:f("body")});a=f(r).width();J.addClass("fancybox-lock-test");d=f(r).width();J.removeClass("fancybox-lock-test");f("").appendTo("head")})})(window,document,jQuery); -------------------------------------------------------------------------------- /js/dat.gui.js: -------------------------------------------------------------------------------- 1 | /** 2 | * dat-gui JavaScript Controller Library 3 | * http://code.google.com/p/dat-gui 4 | * 5 | * Copyright 2011 Data Arts Team, Google Creative Lab 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | */ 13 | 14 | /** @namespace */ 15 | var dat = dat || {}; 16 | 17 | /** @namespace */ 18 | dat.gui = dat.gui || {}; 19 | 20 | /** @namespace */ 21 | dat.utils = dat.utils || {}; 22 | 23 | /** @namespace */ 24 | dat.controllers = dat.controllers || {}; 25 | 26 | /** @namespace */ 27 | dat.dom = dat.dom || {}; 28 | 29 | /** @namespace */ 30 | dat.color = dat.color || {}; 31 | 32 | dat.utils.css = (function () { 33 | return { 34 | load: function (url, doc) { 35 | doc = doc || document; 36 | var link = doc.createElement('link'); 37 | link.type = 'text/css'; 38 | link.rel = 'stylesheet'; 39 | link.href = url; 40 | doc.getElementsByTagName('head')[0].appendChild(link); 41 | }, 42 | inject: function(css, doc) { 43 | doc = doc || document; 44 | var injected = document.createElement('style'); 45 | injected.type = 'text/css'; 46 | injected.innerHTML = css; 47 | doc.getElementsByTagName('head')[0].appendChild(injected); 48 | } 49 | } 50 | })(); 51 | 52 | 53 | dat.utils.common = (function () { 54 | 55 | var ARR_EACH = Array.prototype.forEach; 56 | var ARR_SLICE = Array.prototype.slice; 57 | 58 | /** 59 | * Band-aid methods for things that should be a lot easier in JavaScript. 60 | * Implementation and structure inspired by underscore.js 61 | * http://documentcloud.github.com/underscore/ 62 | */ 63 | 64 | return { 65 | 66 | BREAK: {}, 67 | 68 | extend: function(target) { 69 | 70 | this.each(ARR_SLICE.call(arguments, 1), function(obj) { 71 | 72 | for (var key in obj) 73 | if (!this.isUndefined(obj[key])) 74 | target[key] = obj[key]; 75 | 76 | }, this); 77 | 78 | return target; 79 | 80 | }, 81 | 82 | defaults: function(target) { 83 | 84 | this.each(ARR_SLICE.call(arguments, 1), function(obj) { 85 | 86 | for (var key in obj) 87 | if (this.isUndefined(target[key])) 88 | target[key] = obj[key]; 89 | 90 | }, this); 91 | 92 | return target; 93 | 94 | }, 95 | 96 | compose: function() { 97 | var toCall = ARR_SLICE.call(arguments); 98 | return function() { 99 | var args = ARR_SLICE.call(arguments); 100 | for (var i = toCall.length -1; i >= 0; i--) { 101 | args = [toCall[i].apply(this, args)]; 102 | } 103 | return args[0]; 104 | } 105 | }, 106 | 107 | each: function(obj, itr, scope) { 108 | 109 | if (!obj) return; 110 | 111 | if (ARR_EACH && obj.forEach && obj.forEach === ARR_EACH) { 112 | 113 | obj.forEach(itr, scope); 114 | 115 | } else if (obj.length === obj.length + 0) { // Is number but not NaN 116 | 117 | for (var key = 0, l = obj.length; key < l; key++) 118 | if (key in obj && itr.call(scope, obj[key], key) === this.BREAK) 119 | return; 120 | 121 | } else { 122 | 123 | for (var key in obj) 124 | if (itr.call(scope, obj[key], key) === this.BREAK) 125 | return; 126 | 127 | } 128 | 129 | }, 130 | 131 | defer: function(fnc) { 132 | setTimeout(fnc, 0); 133 | }, 134 | 135 | toArray: function(obj) { 136 | if (obj.toArray) return obj.toArray(); 137 | return ARR_SLICE.call(obj); 138 | }, 139 | 140 | isUndefined: function(obj) { 141 | return obj === undefined; 142 | }, 143 | 144 | isNull: function(obj) { 145 | return obj === null; 146 | }, 147 | 148 | isNaN: function(obj) { 149 | return obj !== obj; 150 | }, 151 | 152 | isArray: Array.isArray || function(obj) { 153 | return obj.constructor === Array; 154 | }, 155 | 156 | isObject: function(obj) { 157 | return obj === Object(obj); 158 | }, 159 | 160 | isNumber: function(obj) { 161 | return obj === obj+0; 162 | }, 163 | 164 | isString: function(obj) { 165 | return obj === obj+''; 166 | }, 167 | 168 | isBoolean: function(obj) { 169 | return obj === false || obj === true; 170 | }, 171 | 172 | isFunction: function(obj) { 173 | return Object.prototype.toString.call(obj) === '[object Function]'; 174 | } 175 | 176 | }; 177 | 178 | })(); 179 | 180 | 181 | dat.controllers.Controller = (function (common) { 182 | 183 | /** 184 | * @class An "abstract" class that represents a given property of an object. 185 | * 186 | * @param {Object} object The object to be manipulated 187 | * @param {string} property The name of the property to be manipulated 188 | * 189 | * @member dat.controllers 190 | */ 191 | var Controller = function(object, property) { 192 | 193 | this.initialValue = object[property]; 194 | 195 | /** 196 | * Those who extend this class will put their DOM elements in here. 197 | * @type {DOMElement} 198 | */ 199 | this.domElement = document.createElement('div'); 200 | 201 | /** 202 | * The object to manipulate 203 | * @type {Object} 204 | */ 205 | this.object = object; 206 | 207 | /** 208 | * The name of the property to manipulate 209 | * @type {String} 210 | */ 211 | this.property = property; 212 | 213 | /** 214 | * The function to be called on change. 215 | * @type {Function} 216 | * @ignore 217 | */ 218 | this.__onChange = undefined; 219 | 220 | /** 221 | * The function to be called on finishing change. 222 | * @type {Function} 223 | * @ignore 224 | */ 225 | this.__onFinishChange = undefined; 226 | 227 | }; 228 | 229 | common.extend( 230 | 231 | Controller.prototype, 232 | 233 | /** @lends dat.controllers.Controller.prototype */ 234 | { 235 | 236 | /** 237 | * Specify that a function fire every time someone changes the value with 238 | * this Controller. 239 | * 240 | * @param {Function} fnc This function will be called whenever the value 241 | * is modified via this Controller. 242 | * @returns {dat.controllers.Controller} this 243 | */ 244 | onChange: function(fnc) { 245 | this.__onChange = fnc; 246 | return this; 247 | }, 248 | 249 | /** 250 | * Specify that a function fire every time someone "finishes" changing 251 | * the value wih this Controller. Useful for values that change 252 | * incrementally like numbers or strings. 253 | * 254 | * @param {Function} fnc This function will be called whenever 255 | * someone "finishes" changing the value via this Controller. 256 | * @returns {dat.controllers.Controller} this 257 | */ 258 | onFinishChange: function(fnc) { 259 | this.__onFinishChange = fnc; 260 | return this; 261 | }, 262 | 263 | /** 264 | * Change the value of object[property] 265 | * 266 | * @param {Object} newValue The new value of object[property] 267 | */ 268 | setValue: function(newValue) { 269 | this.object[this.property] = newValue; 270 | if (this.__onChange) { 271 | this.__onChange.call(this, newValue); 272 | } 273 | this.updateDisplay(); 274 | return this; 275 | }, 276 | 277 | /** 278 | * Gets the value of object[property] 279 | * 280 | * @returns {Object} The current value of object[property] 281 | */ 282 | getValue: function() { 283 | return this.object[this.property]; 284 | }, 285 | 286 | /** 287 | * Refreshes the visual display of a Controller in order to keep sync 288 | * with the object's current value. 289 | * @returns {dat.controllers.Controller} this 290 | */ 291 | updateDisplay: function() { 292 | return this; 293 | }, 294 | 295 | /** 296 | * @returns {Boolean} true if the value has deviated from initialValue 297 | */ 298 | isModified: function() { 299 | return this.initialValue !== this.getValue() 300 | } 301 | 302 | } 303 | 304 | ); 305 | 306 | return Controller; 307 | 308 | 309 | })(dat.utils.common); 310 | 311 | 312 | dat.dom.dom = (function (common) { 313 | 314 | var EVENT_MAP = { 315 | 'HTMLEvents': ['change'], 316 | 'MouseEvents': ['click','mousemove','mousedown','mouseup', 'mouseover'], 317 | 'KeyboardEvents': ['keydown'] 318 | }; 319 | 320 | var EVENT_MAP_INV = {}; 321 | common.each(EVENT_MAP, function(v, k) { 322 | common.each(v, function(e) { 323 | EVENT_MAP_INV[e] = k; 324 | }); 325 | }); 326 | 327 | var CSS_VALUE_PIXELS = /(\d+(\.\d+)?)px/; 328 | 329 | function cssValueToPixels(val) { 330 | 331 | if (val === '0' || common.isUndefined(val)) return 0; 332 | 333 | var match = val.match(CSS_VALUE_PIXELS); 334 | 335 | if (!common.isNull(match)) { 336 | return parseFloat(match[1]); 337 | } 338 | 339 | // TODO ...ems? %? 340 | 341 | return 0; 342 | 343 | } 344 | 345 | /** 346 | * @namespace 347 | * @member dat.dom 348 | */ 349 | var dom = { 350 | 351 | /** 352 | * 353 | * @param elem 354 | * @param selectable 355 | */ 356 | makeSelectable: function(elem, selectable) { 357 | 358 | if (elem === undefined || elem.style === undefined) return; 359 | 360 | elem.onselectstart = selectable ? function() { 361 | return false; 362 | } : function() { 363 | }; 364 | 365 | elem.style.MozUserSelect = selectable ? 'auto' : 'none'; 366 | elem.style.KhtmlUserSelect = selectable ? 'auto' : 'none'; 367 | elem.unselectable = selectable ? 'on' : 'off'; 368 | 369 | }, 370 | 371 | /** 372 | * 373 | * @param elem 374 | * @param horizontal 375 | * @param vertical 376 | */ 377 | makeFullscreen: function(elem, horizontal, vertical) { 378 | 379 | if (common.isUndefined(horizontal)) horizontal = true; 380 | if (common.isUndefined(vertical)) vertical = true; 381 | 382 | elem.style.position = 'absolute'; 383 | 384 | if (horizontal) { 385 | elem.style.left = 0; 386 | elem.style.right = 0; 387 | } 388 | if (vertical) { 389 | elem.style.top = 0; 390 | elem.style.bottom = 0; 391 | } 392 | 393 | }, 394 | 395 | /** 396 | * 397 | * @param elem 398 | * @param eventType 399 | * @param params 400 | */ 401 | fakeEvent: function(elem, eventType, params, aux) { 402 | params = params || {}; 403 | var className = EVENT_MAP_INV[eventType]; 404 | if (!className) { 405 | throw new Error('Event type ' + eventType + ' not supported.'); 406 | } 407 | var evt = document.createEvent(className); 408 | switch (className) { 409 | case 'MouseEvents': 410 | var clientX = params.x || params.clientX || 0; 411 | var clientY = params.y || params.clientY || 0; 412 | evt.initMouseEvent(eventType, params.bubbles || false, 413 | params.cancelable || true, window, params.clickCount || 1, 414 | 0, //screen X 415 | 0, //screen Y 416 | clientX, //client X 417 | clientY, //client Y 418 | false, false, false, false, 0, null); 419 | break; 420 | case 'KeyboardEvents': 421 | var init = evt.initKeyboardEvent || evt.initKeyEvent; // webkit || moz 422 | common.defaults(params, { 423 | cancelable: true, 424 | ctrlKey: false, 425 | altKey: false, 426 | shiftKey: false, 427 | metaKey: false, 428 | keyCode: undefined, 429 | charCode: undefined 430 | }); 431 | init(eventType, params.bubbles || false, 432 | params.cancelable, window, 433 | params.ctrlKey, params.altKey, 434 | params.shiftKey, params.metaKey, 435 | params.keyCode, params.charCode); 436 | break; 437 | default: 438 | evt.initEvent(eventType, params.bubbles || false, 439 | params.cancelable || true); 440 | break; 441 | } 442 | common.defaults(evt, aux); 443 | elem.dispatchEvent(evt); 444 | }, 445 | 446 | /** 447 | * 448 | * @param elem 449 | * @param event 450 | * @param func 451 | * @param bool 452 | */ 453 | bind: function(elem, event, func, bool) { 454 | bool = bool || false; 455 | if (elem.addEventListener) 456 | elem.addEventListener(event, func, bool); 457 | else if (elem.attachEvent) 458 | elem.attachEvent('on' + event, func); 459 | return dom; 460 | }, 461 | 462 | /** 463 | * 464 | * @param elem 465 | * @param event 466 | * @param func 467 | * @param bool 468 | */ 469 | unbind: function(elem, event, func, bool) { 470 | bool = bool || false; 471 | if (elem.removeEventListener) 472 | elem.removeEventListener(event, func, bool); 473 | else if (elem.detachEvent) 474 | elem.detachEvent('on' + event, func); 475 | return dom; 476 | }, 477 | 478 | /** 479 | * 480 | * @param elem 481 | * @param className 482 | */ 483 | addClass: function(elem, className) { 484 | if (elem.className === undefined) { 485 | elem.className = className; 486 | } else if (elem.className !== className) { 487 | var classes = elem.className.split(/ +/); 488 | if (classes.indexOf(className) == -1) { 489 | classes.push(className); 490 | elem.className = classes.join(' ').replace(/^\s+/, '').replace(/\s+$/, ''); 491 | } 492 | } 493 | return dom; 494 | }, 495 | 496 | /** 497 | * 498 | * @param elem 499 | * @param className 500 | */ 501 | removeClass: function(elem, className) { 502 | if (className) { 503 | if (elem.className === undefined) { 504 | // elem.className = className; 505 | } else if (elem.className === className) { 506 | elem.removeAttribute('class'); 507 | } else { 508 | var classes = elem.className.split(/ +/); 509 | var index = classes.indexOf(className); 510 | if (index != -1) { 511 | classes.splice(index, 1); 512 | elem.className = classes.join(' '); 513 | } 514 | } 515 | } else { 516 | elem.className = undefined; 517 | } 518 | return dom; 519 | }, 520 | 521 | hasClass: function(elem, className) { 522 | return new RegExp('(?:^|\\s+)' + className + '(?:\\s+|$)').test(elem.className) || false; 523 | }, 524 | 525 | /** 526 | * 527 | * @param elem 528 | */ 529 | getWidth: function(elem) { 530 | 531 | var style = getComputedStyle(elem); 532 | 533 | return cssValueToPixels(style['border-left-width']) + 534 | cssValueToPixels(style['border-right-width']) + 535 | cssValueToPixels(style['padding-left']) + 536 | cssValueToPixels(style['padding-right']) + 537 | cssValueToPixels(style['width']); 538 | }, 539 | 540 | /** 541 | * 542 | * @param elem 543 | */ 544 | getHeight: function(elem) { 545 | 546 | var style = getComputedStyle(elem); 547 | 548 | return cssValueToPixels(style['border-top-width']) + 549 | cssValueToPixels(style['border-bottom-width']) + 550 | cssValueToPixels(style['padding-top']) + 551 | cssValueToPixels(style['padding-bottom']) + 552 | cssValueToPixels(style['height']); 553 | }, 554 | 555 | /** 556 | * 557 | * @param elem 558 | */ 559 | getOffset: function(elem) { 560 | var offset = {left: 0, top:0}; 561 | if (elem.offsetParent) { 562 | do { 563 | offset.left += elem.offsetLeft; 564 | offset.top += elem.offsetTop; 565 | } while (elem = elem.offsetParent); 566 | } 567 | return offset; 568 | }, 569 | 570 | // http://stackoverflow.com/posts/2684561/revisions 571 | /** 572 | * 573 | * @param elem 574 | */ 575 | isActive: function(elem) { 576 | return elem === document.activeElement && ( elem.type || elem.href ); 577 | } 578 | 579 | }; 580 | 581 | return dom; 582 | 583 | })(dat.utils.common); 584 | 585 | 586 | dat.controllers.OptionController = (function (Controller, dom, common) { 587 | 588 | /** 589 | * @class Provides a select input to alter the property of an object, using a 590 | * list of accepted values. 591 | * 592 | * @extends dat.controllers.Controller 593 | * 594 | * @param {Object} object The object to be manipulated 595 | * @param {string} property The name of the property to be manipulated 596 | * @param {Object|string[]} options A map of labels to acceptable values, or 597 | * a list of acceptable string values. 598 | * 599 | * @member dat.controllers 600 | */ 601 | var OptionController = function(object, property, options) { 602 | 603 | OptionController.superclass.call(this, object, property); 604 | 605 | var _this = this; 606 | 607 | /** 608 | * The drop down menu 609 | * @ignore 610 | */ 611 | this.__select = document.createElement('select'); 612 | 613 | if (common.isArray(options)) { 614 | var map = {}; 615 | common.each(options, function(element) { 616 | map[element] = element; 617 | }); 618 | options = map; 619 | } 620 | 621 | common.each(options, function(value, key) { 622 | 623 | var opt = document.createElement('option'); 624 | opt.innerHTML = key; 625 | opt.setAttribute('value', value); 626 | _this.__select.appendChild(opt); 627 | 628 | }); 629 | 630 | // Acknowledge original value 631 | this.updateDisplay(); 632 | 633 | dom.bind(this.__select, 'change', function() { 634 | var desiredValue = this.options[this.selectedIndex].value; 635 | _this.setValue(desiredValue); 636 | }); 637 | 638 | this.domElement.appendChild(this.__select); 639 | 640 | }; 641 | 642 | OptionController.superclass = Controller; 643 | 644 | common.extend( 645 | 646 | OptionController.prototype, 647 | Controller.prototype, 648 | 649 | { 650 | 651 | setValue: function(v) { 652 | var toReturn = OptionController.superclass.prototype.setValue.call(this, v); 653 | if (this.__onFinishChange) { 654 | this.__onFinishChange.call(this, this.getValue()); 655 | } 656 | return toReturn; 657 | }, 658 | 659 | updateDisplay: function() { 660 | this.__select.value = this.getValue(); 661 | return OptionController.superclass.prototype.updateDisplay.call(this); 662 | } 663 | 664 | } 665 | 666 | ); 667 | 668 | return OptionController; 669 | 670 | })(dat.controllers.Controller, 671 | dat.dom.dom, 672 | dat.utils.common); 673 | 674 | 675 | dat.controllers.NumberController = (function (Controller, common) { 676 | 677 | /** 678 | * @class Represents a given property of an object that is a number. 679 | * 680 | * @extends dat.controllers.Controller 681 | * 682 | * @param {Object} object The object to be manipulated 683 | * @param {string} property The name of the property to be manipulated 684 | * @param {Object} [params] Optional parameters 685 | * @param {Number} [params.min] Minimum allowed value 686 | * @param {Number} [params.max] Maximum allowed value 687 | * @param {Number} [params.step] Increment by which to change value 688 | * 689 | * @member dat.controllers 690 | */ 691 | var NumberController = function(object, property, params) { 692 | 693 | NumberController.superclass.call(this, object, property); 694 | 695 | params = params || {}; 696 | 697 | this.__min = params.min; 698 | this.__max = params.max; 699 | this.__step = params.step; 700 | 701 | if (common.isUndefined(this.__step)) { 702 | 703 | if (this.initialValue == 0) { 704 | this.__impliedStep = 1; // What are we, psychics? 705 | } else { 706 | // Hey Doug, check this out. 707 | this.__impliedStep = Math.pow(10, Math.floor(Math.log(Math.abs(this.initialValue))/Math.LN10))/10; 708 | } 709 | 710 | } else { 711 | 712 | this.__impliedStep = this.__step; 713 | 714 | } 715 | 716 | this.__precision = numDecimals(this.__impliedStep); 717 | 718 | 719 | }; 720 | 721 | NumberController.superclass = Controller; 722 | 723 | common.extend( 724 | 725 | NumberController.prototype, 726 | Controller.prototype, 727 | 728 | /** @lends dat.controllers.NumberController.prototype */ 729 | { 730 | 731 | setValue: function(v) { 732 | 733 | if (this.__min !== undefined && v < this.__min) { 734 | v = this.__min; 735 | } else if (this.__max !== undefined && v > this.__max) { 736 | v = this.__max; 737 | } 738 | 739 | if (this.__step !== undefined && v % this.__step != 0) { 740 | v = Math.round(v / this.__step) * this.__step; 741 | } 742 | 743 | return NumberController.superclass.prototype.setValue.call(this, v); 744 | 745 | }, 746 | 747 | /** 748 | * Specify a minimum value for object[property]. 749 | * 750 | * @param {Number} minValue The minimum value for 751 | * object[property] 752 | * @returns {dat.controllers.NumberController} this 753 | */ 754 | min: function(v) { 755 | this.__min = v; 756 | return this; 757 | }, 758 | 759 | /** 760 | * Specify a maximum value for object[property]. 761 | * 762 | * @param {Number} maxValue The maximum value for 763 | * object[property] 764 | * @returns {dat.controllers.NumberController} this 765 | */ 766 | max: function(v) { 767 | this.__max = v; 768 | return this; 769 | }, 770 | 771 | /** 772 | * Specify a step value that dat.controllers.NumberController 773 | * increments by. 774 | * 775 | * @param {Number} stepValue The step value for 776 | * dat.controllers.NumberController 777 | * @default if minimum and maximum specified increment is 1% of the 778 | * difference otherwise stepValue is 1 779 | * @returns {dat.controllers.NumberController} this 780 | */ 781 | step: function(v) { 782 | this.__step = v; 783 | this.__impliedStep = v; 784 | this.__precision = numDecimals(v); 785 | return this; 786 | } 787 | 788 | } 789 | 790 | ); 791 | 792 | function numDecimals(x) { 793 | x = x.toString(); 794 | if (x.indexOf('.') > -1) { 795 | return x.length - x.indexOf('.') - 1; 796 | } else { 797 | return 0; 798 | } 799 | } 800 | 801 | return NumberController; 802 | 803 | })(dat.controllers.Controller, 804 | dat.utils.common); 805 | 806 | 807 | dat.controllers.NumberControllerBox = (function (NumberController, dom, common) { 808 | 809 | /** 810 | * @class Represents a given property of an object that is a number and 811 | * provides an input element with which to manipulate it. 812 | * 813 | * @extends dat.controllers.Controller 814 | * @extends dat.controllers.NumberController 815 | * 816 | * @param {Object} object The object to be manipulated 817 | * @param {string} property The name of the property to be manipulated 818 | * @param {Object} [params] Optional parameters 819 | * @param {Number} [params.min] Minimum allowed value 820 | * @param {Number} [params.max] Maximum allowed value 821 | * @param {Number} [params.step] Increment by which to change value 822 | * 823 | * @member dat.controllers 824 | */ 825 | var NumberControllerBox = function(object, property, params) { 826 | 827 | this.__truncationSuspended = false; 828 | 829 | NumberControllerBox.superclass.call(this, object, property, params); 830 | 831 | var _this = this; 832 | 833 | /** 834 | * {Number} Previous mouse y position 835 | * @ignore 836 | */ 837 | var prev_y; 838 | 839 | this.__input = document.createElement('input'); 840 | this.__input.setAttribute('type', 'text'); 841 | 842 | // Makes it so manually specified values are not truncated. 843 | 844 | dom.bind(this.__input, 'change', onChange); 845 | dom.bind(this.__input, 'blur', onBlur); 846 | dom.bind(this.__input, 'mousedown', onMouseDown); 847 | dom.bind(this.__input, 'keydown', function(e) { 848 | 849 | // When pressing entire, you can be as precise as you want. 850 | if (e.keyCode === 13) { 851 | _this.__truncationSuspended = true; 852 | this.blur(); 853 | _this.__truncationSuspended = false; 854 | } 855 | 856 | }); 857 | 858 | function onChange() { 859 | var attempted = parseFloat(_this.__input.value); 860 | if (!common.isNaN(attempted)) _this.setValue(attempted); 861 | } 862 | 863 | function onBlur() { 864 | onChange(); 865 | if (_this.__onFinishChange) { 866 | _this.__onFinishChange.call(_this, _this.getValue()); 867 | } 868 | } 869 | 870 | function onMouseDown(e) { 871 | dom.bind(window, 'mousemove', onMouseDrag); 872 | dom.bind(window, 'mouseup', onMouseUp); 873 | prev_y = e.clientY; 874 | } 875 | 876 | function onMouseDrag(e) { 877 | 878 | var diff = prev_y - e.clientY; 879 | _this.setValue(_this.getValue() + diff * _this.__impliedStep); 880 | 881 | prev_y = e.clientY; 882 | 883 | } 884 | 885 | function onMouseUp() { 886 | dom.unbind(window, 'mousemove', onMouseDrag); 887 | dom.unbind(window, 'mouseup', onMouseUp); 888 | } 889 | 890 | this.updateDisplay(); 891 | 892 | this.domElement.appendChild(this.__input); 893 | 894 | }; 895 | 896 | NumberControllerBox.superclass = NumberController; 897 | 898 | common.extend( 899 | 900 | NumberControllerBox.prototype, 901 | NumberController.prototype, 902 | 903 | { 904 | 905 | updateDisplay: function() { 906 | 907 | this.__input.value = this.__truncationSuspended ? this.getValue() : roundToDecimal(this.getValue(), this.__precision); 908 | return NumberControllerBox.superclass.prototype.updateDisplay.call(this); 909 | } 910 | 911 | } 912 | 913 | ); 914 | 915 | function roundToDecimal(value, decimals) { 916 | var tenTo = Math.pow(10, decimals); 917 | return Math.round(value * tenTo) / tenTo; 918 | } 919 | 920 | return NumberControllerBox; 921 | 922 | })(dat.controllers.NumberController, 923 | dat.dom.dom, 924 | dat.utils.common); 925 | 926 | 927 | dat.controllers.NumberControllerSlider = (function (NumberController, dom, css, common, styleSheet) { 928 | 929 | /** 930 | * @class Represents a given property of an object that is a number, contains 931 | * a minimum and maximum, and provides a slider element with which to 932 | * manipulate it. It should be noted that the slider element is made up of 933 | * <div> tags, not the html5 934 | * <slider> element. 935 | * 936 | * @extends dat.controllers.Controller 937 | * @extends dat.controllers.NumberController 938 | * 939 | * @param {Object} object The object to be manipulated 940 | * @param {string} property The name of the property to be manipulated 941 | * @param {Number} minValue Minimum allowed value 942 | * @param {Number} maxValue Maximum allowed value 943 | * @param {Number} stepValue Increment by which to change value 944 | * 945 | * @member dat.controllers 946 | */ 947 | var NumberControllerSlider = function(object, property, min, max, step) { 948 | 949 | NumberControllerSlider.superclass.call(this, object, property, { min: min, max: max, step: step }); 950 | 951 | var _this = this; 952 | 953 | this.__background = document.createElement('div'); 954 | this.__foreground = document.createElement('div'); 955 | 956 | 957 | 958 | dom.bind(this.__background, 'mousedown', onMouseDown); 959 | 960 | dom.addClass(this.__background, 'slider'); 961 | dom.addClass(this.__foreground, 'slider-fg'); 962 | 963 | function onMouseDown(e) { 964 | 965 | dom.bind(window, 'mousemove', onMouseDrag); 966 | dom.bind(window, 'mouseup', onMouseUp); 967 | 968 | onMouseDrag(e); 969 | } 970 | 971 | function onMouseDrag(e) { 972 | 973 | e.preventDefault(); 974 | 975 | var offset = dom.getOffset(_this.__background); 976 | var width = dom.getWidth(_this.__background); 977 | 978 | _this.setValue( 979 | map(e.clientX, offset.left, offset.left + width, _this.__min, _this.__max) 980 | ); 981 | 982 | return false; 983 | 984 | } 985 | 986 | function onMouseUp() { 987 | dom.unbind(window, 'mousemove', onMouseDrag); 988 | dom.unbind(window, 'mouseup', onMouseUp); 989 | if (_this.__onFinishChange) { 990 | _this.__onFinishChange.call(_this, _this.getValue()); 991 | } 992 | } 993 | 994 | this.updateDisplay(); 995 | 996 | this.__background.appendChild(this.__foreground); 997 | this.domElement.appendChild(this.__background); 998 | 999 | }; 1000 | 1001 | NumberControllerSlider.superclass = NumberController; 1002 | 1003 | /** 1004 | * Injects default stylesheet for slider elements. 1005 | */ 1006 | NumberControllerSlider.useDefaultStyles = function() { 1007 | css.inject(styleSheet); 1008 | }; 1009 | 1010 | common.extend( 1011 | 1012 | NumberControllerSlider.prototype, 1013 | NumberController.prototype, 1014 | 1015 | { 1016 | 1017 | updateDisplay: function() { 1018 | var pct = (this.getValue() - this.__min)/(this.__max - this.__min); 1019 | this.__foreground.style.width = pct*100+'%'; 1020 | return NumberControllerSlider.superclass.prototype.updateDisplay.call(this); 1021 | } 1022 | 1023 | } 1024 | 1025 | 1026 | 1027 | ); 1028 | 1029 | function map(v, i1, i2, o1, o2) { 1030 | return o1 + (o2 - o1) * ((v - i1) / (i2 - i1)); 1031 | } 1032 | 1033 | return NumberControllerSlider; 1034 | 1035 | })(dat.controllers.NumberController, 1036 | dat.dom.dom, 1037 | dat.utils.css, 1038 | dat.utils.common, 1039 | "/**\n * dat-gui JavaScript Controller Library\n * http://code.google.com/p/dat-gui\n *\n * Copyright 2011 Data Arts Team, Google Creative Lab\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n */\n\n.slider {\n box-shadow: inset 0 2px 4px rgba(0,0,0,0.15);\n height: 1em;\n border-radius: 1em;\n background-color: #eee;\n padding: 0 0.5em;\n overflow: hidden;\n}\n\n.slider-fg {\n padding: 1px 0 2px 0;\n background-color: #aaa;\n height: 1em;\n margin-left: -0.5em;\n padding-right: 0.5em;\n border-radius: 1em 0 0 1em;\n}\n\n.slider-fg:after {\n display: inline-block;\n border-radius: 1em;\n background-color: #fff;\n border: 1px solid #aaa;\n content: '';\n float: right;\n margin-right: -1em;\n margin-top: -1px;\n height: 0.9em;\n width: 0.9em;\n}"); 1040 | 1041 | 1042 | dat.controllers.FunctionController = (function (Controller, dom, common) { 1043 | 1044 | /** 1045 | * @class Provides a GUI interface to fire a specified method, a property of an object. 1046 | * 1047 | * @extends dat.controllers.Controller 1048 | * 1049 | * @param {Object} object The object to be manipulated 1050 | * @param {string} property The name of the property to be manipulated 1051 | * 1052 | * @member dat.controllers 1053 | */ 1054 | var FunctionController = function(object, property, text) { 1055 | 1056 | FunctionController.superclass.call(this, object, property); 1057 | 1058 | var _this = this; 1059 | 1060 | this.__button = document.createElement('div'); 1061 | this.__button.innerHTML = text === undefined ? 'Fire' : text; 1062 | dom.bind(this.__button, 'click', function(e) { 1063 | e.preventDefault(); 1064 | _this.fire(); 1065 | return false; 1066 | }); 1067 | 1068 | dom.addClass(this.__button, 'button'); 1069 | 1070 | this.domElement.appendChild(this.__button); 1071 | 1072 | 1073 | }; 1074 | 1075 | FunctionController.superclass = Controller; 1076 | 1077 | common.extend( 1078 | 1079 | FunctionController.prototype, 1080 | Controller.prototype, 1081 | { 1082 | 1083 | fire: function() { 1084 | if (this.__onChange) { 1085 | this.__onChange.call(this); 1086 | } 1087 | this.getValue().call(this.object); 1088 | if (this.__onFinishChange) { 1089 | this.__onFinishChange.call(this, this.getValue()); 1090 | } 1091 | } 1092 | } 1093 | 1094 | ); 1095 | 1096 | return FunctionController; 1097 | 1098 | })(dat.controllers.Controller, 1099 | dat.dom.dom, 1100 | dat.utils.common); 1101 | 1102 | 1103 | dat.controllers.BooleanController = (function (Controller, dom, common) { 1104 | 1105 | /** 1106 | * @class Provides a checkbox input to alter the boolean property of an object. 1107 | * @extends dat.controllers.Controller 1108 | * 1109 | * @param {Object} object The object to be manipulated 1110 | * @param {string} property The name of the property to be manipulated 1111 | * 1112 | * @member dat.controllers 1113 | */ 1114 | var BooleanController = function(object, property) { 1115 | 1116 | BooleanController.superclass.call(this, object, property); 1117 | 1118 | var _this = this; 1119 | this.__prev = this.getValue(); 1120 | 1121 | this.__checkbox = document.createElement('input'); 1122 | this.__checkbox.setAttribute('type', 'checkbox'); 1123 | 1124 | 1125 | dom.bind(this.__checkbox, 'change', onChange, false); 1126 | 1127 | this.domElement.appendChild(this.__checkbox); 1128 | 1129 | // Match original value 1130 | this.updateDisplay(); 1131 | 1132 | function onChange() { 1133 | _this.setValue(!_this.__prev); 1134 | } 1135 | 1136 | }; 1137 | 1138 | BooleanController.superclass = Controller; 1139 | 1140 | common.extend( 1141 | 1142 | BooleanController.prototype, 1143 | Controller.prototype, 1144 | 1145 | { 1146 | 1147 | setValue: function(v) { 1148 | var toReturn = BooleanController.superclass.prototype.setValue.call(this, v); 1149 | if (this.__onFinishChange) { 1150 | this.__onFinishChange.call(this, this.getValue()); 1151 | } 1152 | this.__prev = this.getValue(); 1153 | return toReturn; 1154 | }, 1155 | 1156 | updateDisplay: function() { 1157 | 1158 | if (this.getValue() === true) { 1159 | this.__checkbox.setAttribute('checked', 'checked'); 1160 | this.__checkbox.checked = true; 1161 | } else { 1162 | this.__checkbox.checked = false; 1163 | } 1164 | 1165 | return BooleanController.superclass.prototype.updateDisplay.call(this); 1166 | 1167 | } 1168 | 1169 | 1170 | } 1171 | 1172 | ); 1173 | 1174 | return BooleanController; 1175 | 1176 | })(dat.controllers.Controller, 1177 | dat.dom.dom, 1178 | dat.utils.common); 1179 | 1180 | 1181 | dat.color.toString = (function (common) { 1182 | 1183 | return function(color) { 1184 | 1185 | if (color.a == 1 || common.isUndefined(color.a)) { 1186 | 1187 | var s = color.hex.toString(16); 1188 | while (s.length < 6) { 1189 | s = '0' + s; 1190 | } 1191 | 1192 | return '#' + s; 1193 | 1194 | } else { 1195 | 1196 | return 'rgba(' + Math.round(color.r) + ',' + Math.round(color.g) + ',' + Math.round(color.b) + ',' + color.a + ')'; 1197 | 1198 | } 1199 | 1200 | } 1201 | 1202 | })(dat.utils.common); 1203 | 1204 | 1205 | dat.color.interpret = (function (toString, common) { 1206 | 1207 | var result, toReturn; 1208 | 1209 | var interpret = function() { 1210 | 1211 | toReturn = false; 1212 | 1213 | var original = arguments.length > 1 ? common.toArray(arguments) : arguments[0]; 1214 | 1215 | common.each(INTERPRETATIONS, function(family) { 1216 | 1217 | if (family.litmus(original)) { 1218 | 1219 | common.each(family.conversions, function(conversion, conversionName) { 1220 | 1221 | result = conversion.read(original); 1222 | 1223 | if (toReturn === false && result !== false) { 1224 | toReturn = result; 1225 | result.conversionName = conversionName; 1226 | result.conversion = conversion; 1227 | return common.BREAK; 1228 | 1229 | } 1230 | 1231 | }); 1232 | 1233 | return common.BREAK; 1234 | 1235 | } 1236 | 1237 | }); 1238 | 1239 | return toReturn; 1240 | 1241 | }; 1242 | 1243 | var INTERPRETATIONS = [ 1244 | 1245 | // Strings 1246 | { 1247 | 1248 | litmus: common.isString, 1249 | 1250 | conversions: { 1251 | 1252 | THREE_CHAR_HEX: { 1253 | 1254 | read: function(original) { 1255 | 1256 | var test = original.match(/^#([A-F0-9])([A-F0-9])([A-F0-9])$/i); 1257 | if (test === null) return false; 1258 | 1259 | return { 1260 | space: 'HEX', 1261 | hex: parseInt( 1262 | '0x' + 1263 | test[1].toString() + test[1].toString() + 1264 | test[2].toString() + test[2].toString() + 1265 | test[3].toString() + test[3].toString()) 1266 | }; 1267 | 1268 | }, 1269 | 1270 | write: toString 1271 | 1272 | }, 1273 | 1274 | SIX_CHAR_HEX: { 1275 | 1276 | read: function(original) { 1277 | 1278 | var test = original.match(/^#([A-F0-9]{6})$/i); 1279 | if (test === null) return false; 1280 | 1281 | return { 1282 | space: 'HEX', 1283 | hex: parseInt('0x' + test[1].toString()) 1284 | }; 1285 | 1286 | }, 1287 | 1288 | write: toString 1289 | 1290 | }, 1291 | 1292 | CSS_RGB: { 1293 | 1294 | read: function(original) { 1295 | 1296 | var test = original.match(/^rgb\(\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*\)/); 1297 | if (test === null) return false; 1298 | 1299 | return { 1300 | space: 'RGB', 1301 | r: parseFloat(test[1]), 1302 | g: parseFloat(test[2]), 1303 | b: parseFloat(test[3]) 1304 | }; 1305 | 1306 | }, 1307 | 1308 | write: toString 1309 | 1310 | }, 1311 | 1312 | CSS_RGBA: { 1313 | 1314 | read: function(original) { 1315 | 1316 | var test = original.match(/^rgba\(\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*\,\s*(.+)\s*\)/); 1317 | if (test === null) return false; 1318 | 1319 | return { 1320 | space: 'RGB', 1321 | r: parseFloat(test[1]), 1322 | g: parseFloat(test[2]), 1323 | b: parseFloat(test[3]), 1324 | a: parseFloat(test[4]) 1325 | }; 1326 | 1327 | }, 1328 | 1329 | write: toString 1330 | 1331 | } 1332 | 1333 | } 1334 | 1335 | }, 1336 | 1337 | // Numbers 1338 | { 1339 | 1340 | litmus: common.isNumber, 1341 | 1342 | conversions: { 1343 | 1344 | HEX: { 1345 | read: function(original) { 1346 | return { 1347 | space: 'HEX', 1348 | hex: original, 1349 | conversionName: 'HEX' 1350 | } 1351 | }, 1352 | 1353 | write: function(color) { 1354 | return color.hex; 1355 | } 1356 | } 1357 | 1358 | } 1359 | 1360 | }, 1361 | 1362 | // Arrays 1363 | { 1364 | 1365 | litmus: common.isArray, 1366 | 1367 | conversions: { 1368 | 1369 | RGB_ARRAY: { 1370 | read: function(original) { 1371 | if (original.length != 3) return false; 1372 | return { 1373 | space: 'RGB', 1374 | r: original[0], 1375 | g: original[1], 1376 | b: original[2] 1377 | }; 1378 | }, 1379 | 1380 | write: function(color) { 1381 | return [color.r, color.g, color.b]; 1382 | } 1383 | 1384 | }, 1385 | 1386 | RGBA_ARRAY: { 1387 | read: function(original) { 1388 | if (original.length != 4) return false; 1389 | return { 1390 | space: 'RGB', 1391 | r: original[0], 1392 | g: original[1], 1393 | b: original[2], 1394 | a: original[3] 1395 | }; 1396 | }, 1397 | 1398 | write: function(color) { 1399 | return [color.r, color.g, color.b, color.a]; 1400 | } 1401 | 1402 | } 1403 | 1404 | } 1405 | 1406 | }, 1407 | 1408 | // Objects 1409 | { 1410 | 1411 | litmus: common.isObject, 1412 | 1413 | conversions: { 1414 | 1415 | RGBA_OBJ: { 1416 | read: function(original) { 1417 | if (common.isNumber(original.r) && 1418 | common.isNumber(original.g) && 1419 | common.isNumber(original.b) && 1420 | common.isNumber(original.a)) { 1421 | return { 1422 | space: 'RGB', 1423 | r: original.r, 1424 | g: original.g, 1425 | b: original.b, 1426 | a: original.a 1427 | } 1428 | } 1429 | return false; 1430 | }, 1431 | 1432 | write: function(color) { 1433 | return { 1434 | r: color.r, 1435 | g: color.g, 1436 | b: color.b, 1437 | a: color.a 1438 | } 1439 | } 1440 | }, 1441 | 1442 | RGB_OBJ: { 1443 | read: function(original) { 1444 | if (common.isNumber(original.r) && 1445 | common.isNumber(original.g) && 1446 | common.isNumber(original.b)) { 1447 | return { 1448 | space: 'RGB', 1449 | r: original.r, 1450 | g: original.g, 1451 | b: original.b 1452 | } 1453 | } 1454 | return false; 1455 | }, 1456 | 1457 | write: function(color) { 1458 | return { 1459 | r: color.r, 1460 | g: color.g, 1461 | b: color.b 1462 | } 1463 | } 1464 | }, 1465 | 1466 | HSVA_OBJ: { 1467 | read: function(original) { 1468 | if (common.isNumber(original.h) && 1469 | common.isNumber(original.s) && 1470 | common.isNumber(original.v) && 1471 | common.isNumber(original.a)) { 1472 | return { 1473 | space: 'HSV', 1474 | h: original.h, 1475 | s: original.s, 1476 | v: original.v, 1477 | a: original.a 1478 | } 1479 | } 1480 | return false; 1481 | }, 1482 | 1483 | write: function(color) { 1484 | return { 1485 | h: color.h, 1486 | s: color.s, 1487 | v: color.v, 1488 | a: color.a 1489 | } 1490 | } 1491 | }, 1492 | 1493 | HSV_OBJ: { 1494 | read: function(original) { 1495 | if (common.isNumber(original.h) && 1496 | common.isNumber(original.s) && 1497 | common.isNumber(original.v)) { 1498 | return { 1499 | space: 'HSV', 1500 | h: original.h, 1501 | s: original.s, 1502 | v: original.v 1503 | } 1504 | } 1505 | return false; 1506 | }, 1507 | 1508 | write: function(color) { 1509 | return { 1510 | h: color.h, 1511 | s: color.s, 1512 | v: color.v 1513 | } 1514 | } 1515 | 1516 | } 1517 | 1518 | } 1519 | 1520 | } 1521 | 1522 | 1523 | ]; 1524 | 1525 | return interpret; 1526 | 1527 | 1528 | })(dat.color.toString, 1529 | dat.utils.common); 1530 | 1531 | 1532 | dat.GUI = dat.gui.GUI = (function (css, saveDialogueContents, styleSheet, controllerFactory, Controller, BooleanController, FunctionController, NumberControllerBox, NumberControllerSlider, OptionController, ColorController, requestAnimationFrame, CenteredDiv, dom, common) { 1533 | 1534 | css.inject(styleSheet); 1535 | 1536 | /** Outer-most className for GUI's */ 1537 | var CSS_NAMESPACE = 'dg'; 1538 | 1539 | var HIDE_KEY_CODE = 72; 1540 | 1541 | /** The only value shared between the JS and SCSS. Use caution. */ 1542 | var CLOSE_BUTTON_HEIGHT = 20; 1543 | 1544 | var DEFAULT_DEFAULT_PRESET_NAME = 'Default'; 1545 | 1546 | var SUPPORTS_LOCAL_STORAGE = (function() { 1547 | try { 1548 | return 'localStorage' in window && window['localStorage'] !== null; 1549 | } catch (e) { 1550 | return false; 1551 | } 1552 | })(); 1553 | 1554 | var SAVE_DIALOGUE; 1555 | 1556 | /** Have we yet to create an autoPlace GUI? */ 1557 | var auto_place_virgin = true; 1558 | 1559 | /** Fixed position div that auto place GUI's go inside */ 1560 | var auto_place_container; 1561 | 1562 | /** Are we hiding the GUI's ? */ 1563 | var hide = false; 1564 | 1565 | /** GUI's which should be hidden */ 1566 | var hideable_guis = []; 1567 | 1568 | /** 1569 | * A lightweight controller library for JavaScript. It allows you to easily 1570 | * manipulate variables and fire functions on the fly. 1571 | * @class 1572 | * 1573 | * @member dat.gui 1574 | * 1575 | * @param {Object} [params] 1576 | * @param {String} [params.name] The name of this GUI. 1577 | * @param {Object} [params.load] JSON object representing the saved state of 1578 | * this GUI. 1579 | * @param {Boolean} [params.auto=true] 1580 | * @param {dat.gui.GUI} [params.parent] The GUI I'm nested in. 1581 | * @param {Boolean} [params.closed] If true, starts closed 1582 | */ 1583 | var GUI = function(params) { 1584 | 1585 | var _this = this; 1586 | 1587 | /** 1588 | * Outermost DOM Element 1589 | * @type DOMElement 1590 | */ 1591 | this.domElement = document.createElement('div'); 1592 | this.__ul = document.createElement('ul'); 1593 | this.domElement.appendChild(this.__ul); 1594 | 1595 | dom.addClass(this.domElement, CSS_NAMESPACE); 1596 | 1597 | /** 1598 | * Nested GUI's by name 1599 | * @ignore 1600 | */ 1601 | this.__folders = {}; 1602 | 1603 | this.__controllers = []; 1604 | 1605 | /** 1606 | * List of objects I'm remembering for save, only used in top level GUI 1607 | * @ignore 1608 | */ 1609 | this.__rememberedObjects = []; 1610 | 1611 | /** 1612 | * Maps the index of remembered objects to a map of controllers, only used 1613 | * in top level GUI. 1614 | * 1615 | * @private 1616 | * @ignore 1617 | * 1618 | * @example 1619 | * [ 1620 | * { 1621 | * propertyName: Controller, 1622 | * anotherPropertyName: Controller 1623 | * }, 1624 | * { 1625 | * propertyName: Controller 1626 | * } 1627 | * ] 1628 | */ 1629 | this.__rememberedObjectIndecesToControllers = []; 1630 | 1631 | this.__listening = []; 1632 | 1633 | params = params || {}; 1634 | 1635 | // Default parameters 1636 | params = common.defaults(params, { 1637 | autoPlace: true, 1638 | width: GUI.DEFAULT_WIDTH 1639 | }); 1640 | 1641 | params = common.defaults(params, { 1642 | resizable: params.autoPlace, 1643 | hideable: params.autoPlace 1644 | }); 1645 | 1646 | 1647 | if (!common.isUndefined(params.load)) { 1648 | 1649 | // Explicit preset 1650 | if (params.preset) params.load.preset = params.preset; 1651 | 1652 | } else { 1653 | 1654 | params.load = { preset: DEFAULT_DEFAULT_PRESET_NAME }; 1655 | 1656 | } 1657 | 1658 | if (common.isUndefined(params.parent) && params.hideable) { 1659 | hideable_guis.push(this); 1660 | } 1661 | 1662 | // Only root level GUI's are resizable. 1663 | params.resizable = common.isUndefined(params.parent) && params.resizable; 1664 | 1665 | 1666 | if (params.autoPlace && common.isUndefined(params.scrollable)) { 1667 | params.scrollable = true; 1668 | } 1669 | // params.scrollable = common.isUndefined(params.parent) && params.scrollable === true; 1670 | 1671 | // Not part of params because I don't want people passing this in via 1672 | // constructor. Should be a 'remembered' value. 1673 | var use_local_storage = 1674 | SUPPORTS_LOCAL_STORAGE && 1675 | localStorage.getItem(getLocalStorageHash(this, 'isLocal')) === 'true'; 1676 | 1677 | var saveToLocalStorage; 1678 | 1679 | Object.defineProperties(this, 1680 | 1681 | /** @lends dat.gui.GUI.prototype */ 1682 | { 1683 | 1684 | /** 1685 | * The parent GUI 1686 | * @type dat.gui.GUI 1687 | */ 1688 | parent: { 1689 | get: function() { 1690 | return params.parent; 1691 | } 1692 | }, 1693 | 1694 | scrollable: { 1695 | get: function() { 1696 | return params.scrollable; 1697 | } 1698 | }, 1699 | 1700 | /** 1701 | * Handles GUI's element placement for you 1702 | * @type Boolean 1703 | */ 1704 | autoPlace: { 1705 | get: function() { 1706 | return params.autoPlace; 1707 | } 1708 | }, 1709 | 1710 | /** 1711 | * The identifier for a set of saved values 1712 | * @type String 1713 | */ 1714 | preset: { 1715 | 1716 | get: function() { 1717 | if (_this.parent) { 1718 | return _this.getRoot().preset; 1719 | } else { 1720 | return params.load.preset; 1721 | } 1722 | }, 1723 | 1724 | set: function(v) { 1725 | if (_this.parent) { 1726 | _this.getRoot().preset = v; 1727 | } else { 1728 | params.load.preset = v; 1729 | } 1730 | setPresetSelectIndex(this); 1731 | _this.revert(); 1732 | } 1733 | 1734 | }, 1735 | 1736 | /** 1737 | * The width of GUI element 1738 | * @type Number 1739 | */ 1740 | width: { 1741 | get: function() { 1742 | return params.width; 1743 | }, 1744 | set: function(v) { 1745 | params.width = v; 1746 | setWidth(_this, v); 1747 | } 1748 | }, 1749 | 1750 | /** 1751 | * The name of GUI. Used for folders. i.e 1752 | * a folder's name 1753 | * @type String 1754 | */ 1755 | name: { 1756 | get: function() { 1757 | return params.name; 1758 | }, 1759 | set: function(v) { 1760 | // TODO Check for collisions among sibling folders 1761 | params.name = v; 1762 | if (title_row_name) { 1763 | title_row_name.innerHTML = params.name; 1764 | } 1765 | } 1766 | }, 1767 | 1768 | /** 1769 | * Whether the GUI is collapsed or not 1770 | * @type Boolean 1771 | */ 1772 | closed: { 1773 | get: function() { 1774 | return params.closed; 1775 | }, 1776 | set: function(v) { 1777 | params.closed = v; 1778 | if (params.closed) { 1779 | dom.addClass(_this.__ul, GUI.CLASS_CLOSED); 1780 | } else { 1781 | dom.removeClass(_this.__ul, GUI.CLASS_CLOSED); 1782 | } 1783 | // For browsers that aren't going to respect the CSS transition, 1784 | // Lets just check our height against the window height right off 1785 | // the bat. 1786 | this.onResize(); 1787 | 1788 | if (_this.__closeButton) { 1789 | _this.__closeButton.innerHTML = v ? GUI.TEXT_OPEN : GUI.TEXT_CLOSED; 1790 | } 1791 | } 1792 | }, 1793 | 1794 | /** 1795 | * Contains all presets 1796 | * @type Object 1797 | */ 1798 | load: { 1799 | get: function() { 1800 | return params.load; 1801 | } 1802 | }, 1803 | 1804 | /** 1805 | * Determines whether or not to use localStorage as the means for 1806 | * remembering 1807 | * @type Boolean 1808 | */ 1809 | useLocalStorage: { 1810 | 1811 | get: function() { 1812 | return use_local_storage; 1813 | }, 1814 | set: function(bool) { 1815 | if (SUPPORTS_LOCAL_STORAGE) { 1816 | use_local_storage = bool; 1817 | if (bool) { 1818 | dom.bind(window, 'unload', saveToLocalStorage); 1819 | } else { 1820 | dom.unbind(window, 'unload', saveToLocalStorage); 1821 | } 1822 | localStorage.setItem(getLocalStorageHash(_this, 'isLocal'), bool); 1823 | } 1824 | } 1825 | 1826 | } 1827 | 1828 | }); 1829 | 1830 | // Are we a root level GUI? 1831 | if (common.isUndefined(params.parent)) { 1832 | 1833 | params.closed = false; 1834 | 1835 | dom.addClass(this.domElement, GUI.CLASS_MAIN); 1836 | dom.makeSelectable(this.domElement, false); 1837 | 1838 | // Are we supposed to be loading locally? 1839 | if (SUPPORTS_LOCAL_STORAGE) { 1840 | 1841 | if (use_local_storage) { 1842 | 1843 | _this.useLocalStorage = true; 1844 | 1845 | var saved_gui = localStorage.getItem(getLocalStorageHash(this, 'gui')); 1846 | 1847 | if (saved_gui) { 1848 | params.load = JSON.parse(saved_gui); 1849 | } 1850 | 1851 | } 1852 | 1853 | } 1854 | 1855 | this.__closeButton = document.createElement('div'); 1856 | this.__closeButton.innerHTML = GUI.TEXT_CLOSED; 1857 | dom.addClass(this.__closeButton, GUI.CLASS_CLOSE_BUTTON); 1858 | this.domElement.appendChild(this.__closeButton); 1859 | 1860 | dom.bind(this.__closeButton, 'click', function() { 1861 | 1862 | _this.closed = !_this.closed; 1863 | 1864 | 1865 | }); 1866 | 1867 | 1868 | // Oh, you're a nested GUI! 1869 | } else { 1870 | 1871 | if (params.closed === undefined) { 1872 | params.closed = true; 1873 | } 1874 | 1875 | var title_row_name = document.createTextNode(params.name); 1876 | dom.addClass(title_row_name, 'controller-name'); 1877 | 1878 | var title_row = addRow(_this, title_row_name); 1879 | 1880 | var on_click_title = function(e) { 1881 | e.preventDefault(); 1882 | _this.closed = !_this.closed; 1883 | return false; 1884 | }; 1885 | 1886 | dom.addClass(this.__ul, GUI.CLASS_CLOSED); 1887 | 1888 | dom.addClass(title_row, 'title'); 1889 | dom.bind(title_row, 'click', on_click_title); 1890 | 1891 | if (!params.closed) { 1892 | this.closed = false; 1893 | } 1894 | 1895 | } 1896 | 1897 | if (params.autoPlace) { 1898 | 1899 | if (common.isUndefined(params.parent)) { 1900 | 1901 | if (auto_place_virgin) { 1902 | auto_place_container = document.createElement('div'); 1903 | dom.addClass(auto_place_container, CSS_NAMESPACE); 1904 | dom.addClass(auto_place_container, GUI.CLASS_AUTO_PLACE_CONTAINER); 1905 | document.body.appendChild(auto_place_container); 1906 | auto_place_virgin = false; 1907 | } 1908 | 1909 | // Put it in the dom for you. 1910 | auto_place_container.appendChild(this.domElement); 1911 | 1912 | // Apply the auto styles 1913 | dom.addClass(this.domElement, GUI.CLASS_AUTO_PLACE); 1914 | 1915 | } 1916 | 1917 | 1918 | // Make it not elastic. 1919 | if (!this.parent) setWidth(_this, params.width); 1920 | 1921 | } 1922 | 1923 | dom.bind(window, 'resize', function() { _this.onResize() }); 1924 | dom.bind(this.__ul, 'webkitTransitionEnd', function() { _this.onResize(); }); 1925 | dom.bind(this.__ul, 'transitionend', function() { _this.onResize() }); 1926 | dom.bind(this.__ul, 'oTransitionEnd', function() { _this.onResize() }); 1927 | this.onResize(); 1928 | 1929 | 1930 | if (params.resizable) { 1931 | addResizeHandle(this); 1932 | } 1933 | 1934 | saveToLocalStorage = function () { 1935 | if (SUPPORTS_LOCAL_STORAGE && localStorage.getItem(getLocalStorageHash(_this, 'isLocal')) === 'true') { 1936 | localStorage.setItem(getLocalStorageHash(_this, 'gui'), JSON.stringify(_this.getSaveObject())); 1937 | } 1938 | } 1939 | 1940 | // expose this method publicly 1941 | this.saveToLocalStorageIfPossible = saveToLocalStorage; 1942 | 1943 | var root = _this.getRoot(); 1944 | function resetWidth() { 1945 | var root = _this.getRoot(); 1946 | root.width += 1; 1947 | common.defer(function() { 1948 | root.width -= 1; 1949 | }); 1950 | } 1951 | 1952 | if (!params.parent) { 1953 | resetWidth(); 1954 | } 1955 | 1956 | }; 1957 | 1958 | GUI.toggleHide = function() { 1959 | 1960 | hide = !hide; 1961 | common.each(hideable_guis, function(gui) { 1962 | gui.domElement.style.zIndex = hide ? -999 : 999; 1963 | gui.domElement.style.opacity = hide ? 0 : 1; 1964 | }); 1965 | }; 1966 | 1967 | GUI.CLASS_AUTO_PLACE = 'a'; 1968 | GUI.CLASS_AUTO_PLACE_CONTAINER = 'ac'; 1969 | GUI.CLASS_MAIN = 'main'; 1970 | GUI.CLASS_CONTROLLER_ROW = 'cr'; 1971 | GUI.CLASS_TOO_TALL = 'taller-than-window'; 1972 | GUI.CLASS_CLOSED = 'closed'; 1973 | GUI.CLASS_CLOSE_BUTTON = 'close-button'; 1974 | GUI.CLASS_DRAG = 'drag'; 1975 | 1976 | GUI.DEFAULT_WIDTH = 245; 1977 | GUI.TEXT_CLOSED = 'Close Controls'; 1978 | GUI.TEXT_OPEN = 'Open Controls'; 1979 | 1980 | dom.bind(window, 'keydown', function(e) { 1981 | 1982 | if (document.activeElement.type !== 'text' && 1983 | (e.which === HIDE_KEY_CODE || e.keyCode == HIDE_KEY_CODE)) { 1984 | GUI.toggleHide(); 1985 | } 1986 | 1987 | }, false); 1988 | 1989 | common.extend( 1990 | 1991 | GUI.prototype, 1992 | 1993 | /** @lends dat.gui.GUI */ 1994 | { 1995 | 1996 | /** 1997 | * @param object 1998 | * @param property 1999 | * @returns {dat.controllers.Controller} The new controller that was added. 2000 | * @instance 2001 | */ 2002 | add: function(object, property) { 2003 | 2004 | return add( 2005 | this, 2006 | object, 2007 | property, 2008 | { 2009 | factoryArgs: Array.prototype.slice.call(arguments, 2) 2010 | } 2011 | ); 2012 | 2013 | }, 2014 | 2015 | /** 2016 | * @param object 2017 | * @param property 2018 | * @returns {dat.controllers.ColorController} The new controller that was added. 2019 | * @instance 2020 | */ 2021 | addColor: function(object, property) { 2022 | 2023 | return add( 2024 | this, 2025 | object, 2026 | property, 2027 | { 2028 | color: true 2029 | } 2030 | ); 2031 | 2032 | }, 2033 | 2034 | /** 2035 | * @param controller 2036 | * @instance 2037 | */ 2038 | remove: function(controller) { 2039 | 2040 | // TODO listening? 2041 | this.__ul.removeChild(controller.__li); 2042 | this.__controllers.splice(this.__controllers.indexOf(controller), 1); 2043 | var _this = this; 2044 | common.defer(function() { 2045 | _this.onResize(); 2046 | }); 2047 | 2048 | }, 2049 | 2050 | destroy: function() { 2051 | 2052 | if (this.autoPlace) { 2053 | auto_place_container.removeChild(this.domElement); 2054 | } 2055 | 2056 | }, 2057 | 2058 | /** 2059 | * @param name 2060 | * @returns {dat.gui.GUI} The new folder. 2061 | * @throws {Error} if this GUI already has a folder by the specified 2062 | * name 2063 | * @instance 2064 | */ 2065 | addFolder: function(name) { 2066 | 2067 | // We have to prevent collisions on names in order to have a key 2068 | // by which to remember saved values 2069 | if (this.__folders[name] !== undefined) { 2070 | throw new Error('You already have a folder in this GUI by the' + 2071 | ' name "' + name + '"'); 2072 | } 2073 | 2074 | var new_gui_params = { name: name, parent: this }; 2075 | 2076 | // We need to pass down the autoPlace trait so that we can 2077 | // attach event listeners to open/close folder actions to 2078 | // ensure that a scrollbar appears if the window is too short. 2079 | new_gui_params.autoPlace = this.autoPlace; 2080 | 2081 | // Do we have saved appearance data for this folder? 2082 | 2083 | if (this.load && // Anything loaded? 2084 | this.load.folders && // Was my parent a dead-end? 2085 | this.load.folders[name]) { // Did daddy remember me? 2086 | 2087 | // Start me closed if I was closed 2088 | new_gui_params.closed = this.load.folders[name].closed; 2089 | 2090 | // Pass down the loaded data 2091 | new_gui_params.load = this.load.folders[name]; 2092 | 2093 | } 2094 | 2095 | var gui = new GUI(new_gui_params); 2096 | this.__folders[name] = gui; 2097 | 2098 | var li = addRow(this, gui.domElement); 2099 | dom.addClass(li, 'folder'); 2100 | return gui; 2101 | 2102 | }, 2103 | 2104 | open: function() { 2105 | this.closed = false; 2106 | }, 2107 | 2108 | close: function() { 2109 | this.closed = true; 2110 | }, 2111 | 2112 | onResize: function() { 2113 | 2114 | var root = this.getRoot(); 2115 | 2116 | if (root.scrollable) { 2117 | 2118 | var top = dom.getOffset(root.__ul).top; 2119 | var h = 0; 2120 | 2121 | common.each(root.__ul.childNodes, function(node) { 2122 | if (! (root.autoPlace && node === root.__save_row)) 2123 | h += dom.getHeight(node); 2124 | }); 2125 | 2126 | if (window.innerHeight - top - CLOSE_BUTTON_HEIGHT < h) { 2127 | dom.addClass(root.domElement, GUI.CLASS_TOO_TALL); 2128 | root.__ul.style.height = window.innerHeight - top - CLOSE_BUTTON_HEIGHT + 'px'; 2129 | } else { 2130 | dom.removeClass(root.domElement, GUI.CLASS_TOO_TALL); 2131 | root.__ul.style.height = 'auto'; 2132 | } 2133 | 2134 | } 2135 | 2136 | if (root.__resize_handle) { 2137 | common.defer(function() { 2138 | root.__resize_handle.style.height = root.__ul.offsetHeight + 'px'; 2139 | }); 2140 | } 2141 | 2142 | if (root.__closeButton) { 2143 | root.__closeButton.style.width = root.width + 'px'; 2144 | } 2145 | 2146 | }, 2147 | 2148 | /** 2149 | * Mark objects for saving. The order of these objects cannot change as 2150 | * the GUI grows. When remembering new objects, append them to the end 2151 | * of the list. 2152 | * 2153 | * @param {Object...} objects 2154 | * @throws {Error} if not called on a top level GUI. 2155 | * @instance 2156 | */ 2157 | remember: function() { 2158 | 2159 | if (common.isUndefined(SAVE_DIALOGUE)) { 2160 | SAVE_DIALOGUE = new CenteredDiv(); 2161 | SAVE_DIALOGUE.domElement.innerHTML = saveDialogueContents; 2162 | } 2163 | 2164 | if (this.parent) { 2165 | throw new Error("You can only call remember on a top level GUI."); 2166 | } 2167 | 2168 | var _this = this; 2169 | 2170 | common.each(Array.prototype.slice.call(arguments), function(object) { 2171 | if (_this.__rememberedObjects.length == 0) { 2172 | addSaveMenu(_this); 2173 | } 2174 | if (_this.__rememberedObjects.indexOf(object) == -1) { 2175 | _this.__rememberedObjects.push(object); 2176 | } 2177 | }); 2178 | 2179 | if (this.autoPlace) { 2180 | // Set save row width 2181 | setWidth(this, this.width); 2182 | } 2183 | 2184 | }, 2185 | 2186 | /** 2187 | * @returns {dat.gui.GUI} the topmost parent GUI of a nested GUI. 2188 | * @instance 2189 | */ 2190 | getRoot: function() { 2191 | var gui = this; 2192 | while (gui.parent) { 2193 | gui = gui.parent; 2194 | } 2195 | return gui; 2196 | }, 2197 | 2198 | /** 2199 | * @returns {Object} a JSON object representing the current state of 2200 | * this GUI as well as its remembered properties. 2201 | * @instance 2202 | */ 2203 | getSaveObject: function() { 2204 | 2205 | var toReturn = this.load; 2206 | 2207 | toReturn.closed = this.closed; 2208 | 2209 | // Am I remembering any values? 2210 | if (this.__rememberedObjects.length > 0) { 2211 | 2212 | toReturn.preset = this.preset; 2213 | 2214 | if (!toReturn.remembered) { 2215 | toReturn.remembered = {}; 2216 | } 2217 | 2218 | toReturn.remembered[this.preset] = getCurrentPreset(this); 2219 | 2220 | } 2221 | 2222 | toReturn.folders = {}; 2223 | common.each(this.__folders, function(element, key) { 2224 | toReturn.folders[key] = element.getSaveObject(); 2225 | }); 2226 | 2227 | return toReturn; 2228 | 2229 | }, 2230 | 2231 | save: function() { 2232 | 2233 | if (!this.load.remembered) { 2234 | this.load.remembered = {}; 2235 | } 2236 | 2237 | this.load.remembered[this.preset] = getCurrentPreset(this); 2238 | markPresetModified(this, false); 2239 | this.saveToLocalStorageIfPossible(); 2240 | 2241 | }, 2242 | 2243 | saveAs: function(presetName) { 2244 | 2245 | if (!this.load.remembered) { 2246 | 2247 | // Retain default values upon first save 2248 | this.load.remembered = {}; 2249 | this.load.remembered[DEFAULT_DEFAULT_PRESET_NAME] = getCurrentPreset(this, true); 2250 | 2251 | } 2252 | 2253 | this.load.remembered[presetName] = getCurrentPreset(this); 2254 | this.preset = presetName; 2255 | addPresetOption(this, presetName, true); 2256 | this.saveToLocalStorageIfPossible(); 2257 | 2258 | }, 2259 | 2260 | revert: function(gui) { 2261 | 2262 | common.each(this.__controllers, function(controller) { 2263 | // Make revert work on Default. 2264 | if (!this.getRoot().load.remembered) { 2265 | controller.setValue(controller.initialValue); 2266 | } else { 2267 | recallSavedValue(gui || this.getRoot(), controller); 2268 | } 2269 | }, this); 2270 | 2271 | common.each(this.__folders, function(folder) { 2272 | folder.revert(folder); 2273 | }); 2274 | 2275 | if (!gui) { 2276 | markPresetModified(this.getRoot(), false); 2277 | } 2278 | 2279 | 2280 | }, 2281 | 2282 | listen: function(controller) { 2283 | 2284 | var init = this.__listening.length == 0; 2285 | this.__listening.push(controller); 2286 | if (init) updateDisplays(this.__listening); 2287 | 2288 | } 2289 | 2290 | } 2291 | 2292 | ); 2293 | 2294 | function add(gui, object, property, params) { 2295 | 2296 | if (object[property] === undefined) { 2297 | throw new Error("Object " + object + " has no property \"" + property + "\""); 2298 | } 2299 | 2300 | var controller; 2301 | 2302 | if (params.color) { 2303 | 2304 | controller = new ColorController(object, property); 2305 | 2306 | } else { 2307 | 2308 | var factoryArgs = [object,property].concat(params.factoryArgs); 2309 | controller = controllerFactory.apply(gui, factoryArgs); 2310 | 2311 | } 2312 | 2313 | if (params.before instanceof Controller) { 2314 | params.before = params.before.__li; 2315 | } 2316 | 2317 | recallSavedValue(gui, controller); 2318 | 2319 | dom.addClass(controller.domElement, 'c'); 2320 | 2321 | var name = document.createElement('span'); 2322 | dom.addClass(name, 'property-name'); 2323 | name.innerHTML = controller.property; 2324 | 2325 | var container = document.createElement('div'); 2326 | container.appendChild(name); 2327 | container.appendChild(controller.domElement); 2328 | 2329 | var li = addRow(gui, container, params.before); 2330 | 2331 | dom.addClass(li, GUI.CLASS_CONTROLLER_ROW); 2332 | dom.addClass(li, typeof controller.getValue()); 2333 | 2334 | augmentController(gui, li, controller); 2335 | 2336 | gui.__controllers.push(controller); 2337 | 2338 | return controller; 2339 | 2340 | } 2341 | 2342 | /** 2343 | * Add a row to the end of the GUI or before another row. 2344 | * 2345 | * @param gui 2346 | * @param [dom] If specified, inserts the dom content in the new row 2347 | * @param [liBefore] If specified, places the new row before another row 2348 | */ 2349 | function addRow(gui, dom, liBefore) { 2350 | var li = document.createElement('li'); 2351 | if (dom) li.appendChild(dom); 2352 | if (liBefore) { 2353 | gui.__ul.insertBefore(li, params.before); 2354 | } else { 2355 | gui.__ul.appendChild(li); 2356 | } 2357 | gui.onResize(); 2358 | return li; 2359 | } 2360 | 2361 | function augmentController(gui, li, controller) { 2362 | 2363 | controller.__li = li; 2364 | controller.__gui = gui; 2365 | 2366 | common.extend(controller, { 2367 | 2368 | options: function(options) { 2369 | 2370 | if (arguments.length > 1) { 2371 | controller.remove(); 2372 | 2373 | return add( 2374 | gui, 2375 | controller.object, 2376 | controller.property, 2377 | { 2378 | before: controller.__li.nextElementSibling, 2379 | factoryArgs: [common.toArray(arguments)] 2380 | } 2381 | ); 2382 | 2383 | } 2384 | 2385 | if (common.isArray(options) || common.isObject(options)) { 2386 | controller.remove(); 2387 | 2388 | return add( 2389 | gui, 2390 | controller.object, 2391 | controller.property, 2392 | { 2393 | before: controller.__li.nextElementSibling, 2394 | factoryArgs: [options] 2395 | } 2396 | ); 2397 | 2398 | } 2399 | 2400 | }, 2401 | 2402 | name: function(v) { 2403 | controller.__li.firstElementChild.firstElementChild.innerHTML = v; 2404 | return controller; 2405 | }, 2406 | 2407 | listen: function() { 2408 | controller.__gui.listen(controller); 2409 | return controller; 2410 | }, 2411 | 2412 | remove: function() { 2413 | controller.__gui.remove(controller); 2414 | return controller; 2415 | } 2416 | 2417 | }); 2418 | 2419 | // All sliders should be accompanied by a box. 2420 | if (controller instanceof NumberControllerSlider) { 2421 | 2422 | var box = new NumberControllerBox(controller.object, controller.property, 2423 | { min: controller.__min, max: controller.__max, step: controller.__step }); 2424 | 2425 | common.each(['updateDisplay', 'onChange', 'onFinishChange'], function(method) { 2426 | var pc = controller[method]; 2427 | var pb = box[method]; 2428 | controller[method] = box[method] = function() { 2429 | var args = Array.prototype.slice.call(arguments); 2430 | pc.apply(controller, args); 2431 | return pb.apply(box, args); 2432 | } 2433 | }); 2434 | 2435 | dom.addClass(li, 'has-slider'); 2436 | controller.domElement.insertBefore(box.domElement, controller.domElement.firstElementChild); 2437 | 2438 | } 2439 | else if (controller instanceof NumberControllerBox) { 2440 | 2441 | var r = function(returned) { 2442 | 2443 | // Have we defined both boundaries? 2444 | if (common.isNumber(controller.__min) && common.isNumber(controller.__max)) { 2445 | 2446 | // Well, then lets just replace this with a slider. 2447 | controller.remove(); 2448 | return add( 2449 | gui, 2450 | controller.object, 2451 | controller.property, 2452 | { 2453 | before: controller.__li.nextElementSibling, 2454 | factoryArgs: [controller.__min, controller.__max, controller.__step] 2455 | }); 2456 | 2457 | } 2458 | 2459 | return returned; 2460 | 2461 | }; 2462 | 2463 | controller.min = common.compose(r, controller.min); 2464 | controller.max = common.compose(r, controller.max); 2465 | 2466 | } 2467 | else if (controller instanceof BooleanController) { 2468 | 2469 | dom.bind(li, 'click', function() { 2470 | dom.fakeEvent(controller.__checkbox, 'click'); 2471 | }); 2472 | 2473 | dom.bind(controller.__checkbox, 'click', function(e) { 2474 | e.stopPropagation(); // Prevents double-toggle 2475 | }) 2476 | 2477 | } 2478 | else if (controller instanceof FunctionController) { 2479 | 2480 | dom.bind(li, 'click', function() { 2481 | dom.fakeEvent(controller.__button, 'click'); 2482 | }); 2483 | 2484 | dom.bind(li, 'mouseover', function() { 2485 | dom.addClass(controller.__button, 'hover'); 2486 | }); 2487 | 2488 | dom.bind(li, 'mouseout', function() { 2489 | dom.removeClass(controller.__button, 'hover'); 2490 | }); 2491 | 2492 | } 2493 | else if (controller instanceof ColorController) { 2494 | 2495 | dom.addClass(li, 'color'); 2496 | controller.updateDisplay = common.compose(function(r) { 2497 | li.style.borderLeftColor = controller.__color.toString(); 2498 | return r; 2499 | }, controller.updateDisplay); 2500 | 2501 | controller.updateDisplay(); 2502 | 2503 | } 2504 | 2505 | controller.setValue = common.compose(function(r) { 2506 | if (gui.getRoot().__preset_select && controller.isModified()) { 2507 | markPresetModified(gui.getRoot(), true); 2508 | } 2509 | return r; 2510 | }, controller.setValue); 2511 | 2512 | } 2513 | 2514 | function recallSavedValue(gui, controller) { 2515 | 2516 | // Find the topmost GUI, that's where remembered objects live. 2517 | var root = gui.getRoot(); 2518 | 2519 | // Does the object we're controlling match anything we've been told to 2520 | // remember? 2521 | var matched_index = root.__rememberedObjects.indexOf(controller.object); 2522 | 2523 | // Why yes, it does! 2524 | if (matched_index != -1) { 2525 | 2526 | // Let me fetch a map of controllers for thcommon.isObject. 2527 | var controller_map = 2528 | root.__rememberedObjectIndecesToControllers[matched_index]; 2529 | 2530 | // Ohp, I believe this is the first controller we've created for this 2531 | // object. Lets make the map fresh. 2532 | if (controller_map === undefined) { 2533 | controller_map = {}; 2534 | root.__rememberedObjectIndecesToControllers[matched_index] = 2535 | controller_map; 2536 | } 2537 | 2538 | // Keep track of this controller 2539 | controller_map[controller.property] = controller; 2540 | 2541 | // Okay, now have we saved any values for this controller? 2542 | if (root.load && root.load.remembered) { 2543 | 2544 | var preset_map = root.load.remembered; 2545 | 2546 | // Which preset are we trying to load? 2547 | var preset; 2548 | 2549 | if (preset_map[gui.preset]) { 2550 | 2551 | preset = preset_map[gui.preset]; 2552 | 2553 | } else if (preset_map[DEFAULT_DEFAULT_PRESET_NAME]) { 2554 | 2555 | // Uhh, you can have the default instead? 2556 | preset = preset_map[DEFAULT_DEFAULT_PRESET_NAME]; 2557 | 2558 | } else { 2559 | 2560 | // Nada. 2561 | 2562 | return; 2563 | 2564 | } 2565 | 2566 | 2567 | // Did the loaded object remember thcommon.isObject? 2568 | if (preset[matched_index] && 2569 | 2570 | // Did we remember this particular property? 2571 | preset[matched_index][controller.property] !== undefined) { 2572 | 2573 | // We did remember something for this guy ... 2574 | var value = preset[matched_index][controller.property]; 2575 | 2576 | // And that's what it is. 2577 | controller.initialValue = value; 2578 | controller.setValue(value); 2579 | 2580 | } 2581 | 2582 | } 2583 | 2584 | } 2585 | 2586 | } 2587 | 2588 | function getLocalStorageHash(gui, key) { 2589 | // TODO how does this deal with multiple GUI's? 2590 | return document.location.href + '.' + key; 2591 | 2592 | } 2593 | 2594 | function addSaveMenu(gui) { 2595 | 2596 | var div = gui.__save_row = document.createElement('li'); 2597 | 2598 | dom.addClass(gui.domElement, 'has-save'); 2599 | 2600 | gui.__ul.insertBefore(div, gui.__ul.firstChild); 2601 | 2602 | dom.addClass(div, 'save-row'); 2603 | 2604 | var gears = document.createElement('span'); 2605 | gears.innerHTML = ' '; 2606 | dom.addClass(gears, 'button gears'); 2607 | 2608 | // TODO replace with FunctionController 2609 | var button = document.createElement('span'); 2610 | button.innerHTML = 'Save'; 2611 | dom.addClass(button, 'button'); 2612 | dom.addClass(button, 'save'); 2613 | 2614 | var button2 = document.createElement('span'); 2615 | button2.innerHTML = 'New'; 2616 | dom.addClass(button2, 'button'); 2617 | dom.addClass(button2, 'save-as'); 2618 | 2619 | var button3 = document.createElement('span'); 2620 | button3.innerHTML = 'Revert'; 2621 | dom.addClass(button3, 'button'); 2622 | dom.addClass(button3, 'revert'); 2623 | 2624 | var select = gui.__preset_select = document.createElement('select'); 2625 | 2626 | if (gui.load && gui.load.remembered) { 2627 | 2628 | common.each(gui.load.remembered, function(value, key) { 2629 | addPresetOption(gui, key, key == gui.preset); 2630 | }); 2631 | 2632 | } else { 2633 | addPresetOption(gui, DEFAULT_DEFAULT_PRESET_NAME, false); 2634 | } 2635 | 2636 | dom.bind(select, 'change', function() { 2637 | 2638 | 2639 | for (var index = 0; index < gui.__preset_select.length; index++) { 2640 | gui.__preset_select[index].innerHTML = gui.__preset_select[index].value; 2641 | } 2642 | 2643 | gui.preset = this.value; 2644 | 2645 | }); 2646 | 2647 | div.appendChild(select); 2648 | div.appendChild(gears); 2649 | div.appendChild(button); 2650 | div.appendChild(button2); 2651 | div.appendChild(button3); 2652 | 2653 | if (SUPPORTS_LOCAL_STORAGE) { 2654 | 2655 | var saveLocally = document.getElementById('dg-save-locally'); 2656 | var explain = document.getElementById('dg-local-explain'); 2657 | 2658 | saveLocally.style.display = 'block'; 2659 | 2660 | var localStorageCheckBox = document.getElementById('dg-local-storage'); 2661 | 2662 | if (localStorage.getItem(getLocalStorageHash(gui, 'isLocal')) === 'true') { 2663 | localStorageCheckBox.setAttribute('checked', 'checked'); 2664 | } 2665 | 2666 | function showHideExplain() { 2667 | explain.style.display = gui.useLocalStorage ? 'block' : 'none'; 2668 | } 2669 | 2670 | showHideExplain(); 2671 | 2672 | // TODO: Use a boolean controller, fool! 2673 | dom.bind(localStorageCheckBox, 'change', function() { 2674 | gui.useLocalStorage = !gui.useLocalStorage; 2675 | showHideExplain(); 2676 | }); 2677 | 2678 | } 2679 | 2680 | var newConstructorTextArea = document.getElementById('dg-new-constructor'); 2681 | 2682 | dom.bind(newConstructorTextArea, 'keydown', function(e) { 2683 | if (e.metaKey && (e.which === 67 || e.keyCode == 67)) { 2684 | SAVE_DIALOGUE.hide(); 2685 | } 2686 | }); 2687 | 2688 | dom.bind(gears, 'click', function() { 2689 | newConstructorTextArea.innerHTML = JSON.stringify(gui.getSaveObject(), undefined, 2); 2690 | SAVE_DIALOGUE.show(); 2691 | newConstructorTextArea.focus(); 2692 | newConstructorTextArea.select(); 2693 | }); 2694 | 2695 | dom.bind(button, 'click', function() { 2696 | gui.save(); 2697 | }); 2698 | 2699 | dom.bind(button2, 'click', function() { 2700 | var presetName = prompt('Enter a new preset name.'); 2701 | if (presetName) gui.saveAs(presetName); 2702 | }); 2703 | 2704 | dom.bind(button3, 'click', function() { 2705 | gui.revert(); 2706 | }); 2707 | 2708 | // div.appendChild(button2); 2709 | 2710 | } 2711 | 2712 | function addResizeHandle(gui) { 2713 | 2714 | gui.__resize_handle = document.createElement('div'); 2715 | 2716 | common.extend(gui.__resize_handle.style, { 2717 | 2718 | width: '6px', 2719 | marginLeft: '-3px', 2720 | height: '200px', 2721 | cursor: 'ew-resize', 2722 | position: 'absolute' 2723 | // border: '1px solid blue' 2724 | 2725 | }); 2726 | 2727 | var pmouseX; 2728 | 2729 | dom.bind(gui.__resize_handle, 'mousedown', dragStart); 2730 | dom.bind(gui.__closeButton, 'mousedown', dragStart); 2731 | 2732 | gui.domElement.insertBefore(gui.__resize_handle, gui.domElement.firstElementChild); 2733 | 2734 | function dragStart(e) { 2735 | 2736 | e.preventDefault(); 2737 | 2738 | pmouseX = e.clientX; 2739 | 2740 | dom.addClass(gui.__closeButton, GUI.CLASS_DRAG); 2741 | dom.bind(window, 'mousemove', drag); 2742 | dom.bind(window, 'mouseup', dragStop); 2743 | 2744 | return false; 2745 | 2746 | } 2747 | 2748 | function drag(e) { 2749 | 2750 | e.preventDefault(); 2751 | 2752 | gui.width += pmouseX - e.clientX; 2753 | gui.onResize(); 2754 | pmouseX = e.clientX; 2755 | 2756 | return false; 2757 | 2758 | } 2759 | 2760 | function dragStop() { 2761 | 2762 | dom.removeClass(gui.__closeButton, GUI.CLASS_DRAG); 2763 | dom.unbind(window, 'mousemove', drag); 2764 | dom.unbind(window, 'mouseup', dragStop); 2765 | 2766 | } 2767 | 2768 | } 2769 | 2770 | function setWidth(gui, w) { 2771 | gui.domElement.style.width = w + 'px'; 2772 | // Auto placed save-rows are position fixed, so we have to 2773 | // set the width manually if we want it to bleed to the edge 2774 | if (gui.__save_row && gui.autoPlace) { 2775 | gui.__save_row.style.width = w + 'px'; 2776 | }if (gui.__closeButton) { 2777 | gui.__closeButton.style.width = w + 'px'; 2778 | } 2779 | } 2780 | 2781 | function getCurrentPreset(gui, useInitialValues) { 2782 | 2783 | var toReturn = {}; 2784 | 2785 | // For each object I'm remembering 2786 | common.each(gui.__rememberedObjects, function(val, index) { 2787 | 2788 | var saved_values = {}; 2789 | 2790 | // The controllers I've made for thcommon.isObject by property 2791 | var controller_map = 2792 | gui.__rememberedObjectIndecesToControllers[index]; 2793 | 2794 | // Remember each value for each property 2795 | common.each(controller_map, function(controller, property) { 2796 | saved_values[property] = useInitialValues ? controller.initialValue : controller.getValue(); 2797 | }); 2798 | 2799 | // Save the values for thcommon.isObject 2800 | toReturn[index] = saved_values; 2801 | 2802 | }); 2803 | 2804 | return toReturn; 2805 | 2806 | } 2807 | 2808 | function addPresetOption(gui, name, setSelected) { 2809 | var opt = document.createElement('option'); 2810 | opt.innerHTML = name; 2811 | opt.value = name; 2812 | gui.__preset_select.appendChild(opt); 2813 | if (setSelected) { 2814 | gui.__preset_select.selectedIndex = gui.__preset_select.length - 1; 2815 | } 2816 | } 2817 | 2818 | function setPresetSelectIndex(gui) { 2819 | for (var index = 0; index < gui.__preset_select.length; index++) { 2820 | if (gui.__preset_select[index].value == gui.preset) { 2821 | gui.__preset_select.selectedIndex = index; 2822 | } 2823 | } 2824 | } 2825 | 2826 | function markPresetModified(gui, modified) { 2827 | var opt = gui.__preset_select[gui.__preset_select.selectedIndex]; 2828 | // console.log('mark', modified, opt); 2829 | if (modified) { 2830 | opt.innerHTML = opt.value + "*"; 2831 | } else { 2832 | opt.innerHTML = opt.value; 2833 | } 2834 | } 2835 | 2836 | function updateDisplays(controllerArray) { 2837 | 2838 | 2839 | if (controllerArray.length != 0) { 2840 | 2841 | requestAnimationFrame(function() { 2842 | updateDisplays(controllerArray); 2843 | }); 2844 | 2845 | } 2846 | 2847 | common.each(controllerArray, function(c) { 2848 | c.updateDisplay(); 2849 | }); 2850 | 2851 | } 2852 | 2853 | return GUI; 2854 | 2855 | })(dat.utils.css, 2856 | "
\n\n Here's the new load parameter for your GUI's constructor:\n\n \n\n
\n\n Automatically save\n values to localStorage on exit.\n\n
The values saved to localStorage will\n override those passed to dat.GUI's constructor. This makes it\n easier to work incrementally, but localStorage is fragile,\n and your friends may not see the same values you do.\n \n
\n \n
\n\n
", 2857 | ".dg {\n /** Clear list styles */\n /* Auto-place container */\n /* Auto-placed GUI's */\n /* Line items that don't contain folders. */\n /** Folder names */\n /** Hides closed items */\n /** Controller row */\n /** Name-half (left) */\n /** Controller-half (right) */\n /** Controller placement */\n /** Shorter number boxes when slider is present. */\n /** Ensure the entire boolean and function row shows a hand */ }\n .dg ul {\n list-style: none;\n margin: 0;\n padding: 0;\n width: 100%;\n clear: both; }\n .dg.ac {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n height: 0;\n z-index: 0; }\n .dg:not(.ac) .main {\n /** Exclude mains in ac so that we don't hide close button */\n overflow: hidden; }\n .dg.main {\n -webkit-transition: opacity 0.1s linear;\n -o-transition: opacity 0.1s linear;\n -moz-transition: opacity 0.1s linear;\n transition: opacity 0.1s linear; }\n .dg.main.taller-than-window {\n overflow-y: auto; }\n .dg.main.taller-than-window .close-button {\n opacity: 1;\n /* TODO, these are style notes */\n margin-top: -1px;\n border-top: 1px solid #2c2c2c; }\n .dg.main ul.closed .close-button {\n opacity: 1 !important; }\n .dg.main:hover .close-button,\n .dg.main .close-button.drag {\n opacity: 1; }\n .dg.main .close-button {\n /*opacity: 0;*/\n -webkit-transition: opacity 0.1s linear;\n -o-transition: opacity 0.1s linear;\n -moz-transition: opacity 0.1s linear;\n transition: opacity 0.1s linear;\n border: 0;\n position: absolute;\n line-height: 19px;\n height: 20px;\n /* TODO, these are style notes */\n cursor: pointer;\n text-align: center;\n background-color: #000; }\n .dg.main .close-button:hover {\n background-color: #111; }\n .dg.a {\n float: right;\n margin-right: 15px;\n overflow-x: hidden; }\n .dg.a.has-save > ul {\n margin-top: 27px; }\n .dg.a.has-save > ul.closed {\n margin-top: 0; }\n .dg.a .save-row {\n position: fixed;\n top: 0;\n z-index: 1002; }\n .dg li {\n -webkit-transition: height 0.1s ease-out;\n -o-transition: height 0.1s ease-out;\n -moz-transition: height 0.1s ease-out;\n transition: height 0.1s ease-out; }\n .dg li:not(.folder) {\n cursor: auto;\n height: 27px;\n line-height: 27px;\n overflow: hidden;\n padding: 0 4px 0 5px; }\n .dg li.folder {\n padding: 0;\n border-left: 4px solid rgba(0, 0, 0, 0); }\n .dg li.title {\n cursor: pointer;\n margin-left: -4px; }\n .dg .closed li:not(.title),\n .dg .closed ul li,\n .dg .closed ul li > * {\n height: 0;\n overflow: hidden;\n border: 0; }\n .dg .cr {\n clear: both;\n padding-left: 3px;\n height: 27px; }\n .dg .property-name {\n cursor: default;\n float: left;\n clear: left;\n width: 40%;\n overflow: hidden;\n text-overflow: ellipsis; }\n .dg .c {\n float: left;\n width: 60%; }\n .dg .c input[type=text] {\n border: 0;\n margin-top: 4px;\n padding: 3px;\n width: 100%;\n float: right; }\n .dg .has-slider input[type=text] {\n width: 30%;\n /*display: none;*/\n margin-left: 0; }\n .dg .slider {\n float: left;\n width: 66%;\n margin-left: -5px;\n margin-right: 0;\n height: 19px;\n margin-top: 4px; }\n .dg .slider-fg {\n height: 100%; }\n .dg .c input[type=checkbox] {\n margin-top: 9px; }\n .dg .c select {\n margin-top: 5px; }\n .dg .cr.function,\n .dg .cr.function .property-name,\n .dg .cr.function *,\n .dg .cr.boolean,\n .dg .cr.boolean * {\n cursor: pointer; }\n .dg .selector {\n display: none;\n position: absolute;\n margin-left: -9px;\n margin-top: 23px;\n z-index: 10; }\n .dg .c:hover .selector,\n .dg .selector.drag {\n display: block; }\n .dg li.save-row {\n padding: 0; }\n .dg li.save-row .button {\n display: inline-block;\n padding: 0px 6px; }\n .dg.dialogue {\n background-color: #222;\n width: 460px;\n padding: 15px;\n font-size: 13px;\n line-height: 15px; }\n\n/* TODO Separate style and structure */\n#dg-new-constructor {\n padding: 10px;\n color: #222;\n font-family: Monaco, monospace;\n font-size: 10px;\n border: 0;\n resize: none;\n box-shadow: inset 1px 1px 1px #888;\n word-wrap: break-word;\n margin: 12px 0;\n display: block;\n width: 440px;\n overflow-y: scroll;\n height: 100px;\n position: relative; }\n\n#dg-local-explain {\n display: none;\n font-size: 11px;\n line-height: 17px;\n border-radius: 3px;\n background-color: #333;\n padding: 8px;\n margin-top: 10px; }\n #dg-local-explain code {\n font-size: 10px; }\n\n#dat-gui-save-locally {\n display: none; }\n\n/** Main type */\n.dg {\n color: #eee;\n font: 11px 'Lucida Grande', sans-serif;\n text-shadow: 0 -1px 0 #111;\n /** Auto place */\n /* Controller row,
  • */\n /** Controllers */ }\n .dg.main {\n /** Scrollbar */ }\n .dg.main::-webkit-scrollbar {\n width: 5px;\n background: #1a1a1a; }\n .dg.main::-webkit-scrollbar-corner {\n height: 0;\n display: none; }\n .dg.main::-webkit-scrollbar-thumb {\n border-radius: 5px;\n background: #676767; }\n .dg li:not(.folder) {\n background: #1a1a1a;\n border-bottom: 1px solid #2c2c2c; }\n .dg li.save-row {\n line-height: 25px;\n background: #dad5cb;\n border: 0; }\n .dg li.save-row select {\n margin-left: 5px;\n width: 108px; }\n .dg li.save-row .button {\n margin-left: 5px;\n margin-top: 1px;\n border-radius: 2px;\n font-size: 9px;\n line-height: 7px;\n padding: 4px 4px 5px 4px;\n background: #c5bdad;\n color: #fff;\n text-shadow: 0 1px 0 #b0a58f;\n box-shadow: 0 -1px 0 #b0a58f;\n cursor: pointer; }\n .dg li.save-row .button.gears {\n background: #c5bdad url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAANCAYAAAB/9ZQ7AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQJJREFUeNpiYKAU/P//PwGIC/ApCABiBSAW+I8AClAcgKxQ4T9hoMAEUrxx2QSGN6+egDX+/vWT4e7N82AMYoPAx/evwWoYoSYbACX2s7KxCxzcsezDh3evFoDEBYTEEqycggWAzA9AuUSQQgeYPa9fPv6/YWm/Acx5IPb7ty/fw+QZblw67vDs8R0YHyQhgObx+yAJkBqmG5dPPDh1aPOGR/eugW0G4vlIoTIfyFcA+QekhhHJhPdQxbiAIguMBTQZrPD7108M6roWYDFQiIAAv6Aow/1bFwXgis+f2LUAynwoIaNcz8XNx3Dl7MEJUDGQpx9gtQ8YCueB+D26OECAAQDadt7e46D42QAAAABJRU5ErkJggg==) 2px 1px no-repeat;\n height: 7px;\n width: 8px; }\n .dg li.save-row .button:hover {\n background-color: #bab19e;\n box-shadow: 0 -1px 0 #b0a58f; }\n .dg li.folder {\n border-bottom: 0; }\n .dg li.title {\n padding-left: 16px;\n background: black url(data:image/gif;base64,R0lGODlhBQAFAJEAAP////Pz8////////yH5BAEAAAIALAAAAAAFAAUAAAIIlI+hKgFxoCgAOw==) 6px 10px no-repeat;\n cursor: pointer;\n border-bottom: 1px solid rgba(255, 255, 255, 0.2); }\n .dg .closed li.title {\n background-image: url(data:image/gif;base64,R0lGODlhBQAFAJEAAP////Pz8////////yH5BAEAAAIALAAAAAAFAAUAAAIIlGIWqMCbWAEAOw==); }\n .dg .cr.boolean {\n border-left: 3px solid #806787; }\n .dg .cr.function {\n border-left: 3px solid #e61d5f; }\n .dg .cr.number {\n border-left: 3px solid #2fa1d6; }\n .dg .cr.number input[type=text] {\n color: #2fa1d6; }\n .dg .cr.string {\n border-left: 3px solid #1ed36f; }\n .dg .cr.string input[type=text] {\n color: #1ed36f; }\n .dg .cr.function:hover, .dg .cr.boolean:hover {\n background: #111; }\n .dg .c input[type=text] {\n background: #303030;\n outline: none; }\n .dg .c input[type=text]:hover {\n background: #3c3c3c; }\n .dg .c input[type=text]:focus {\n background: #494949;\n color: #fff; }\n .dg .c .slider {\n background: #303030;\n cursor: ew-resize; }\n .dg .c .slider-fg {\n background: #2fa1d6; }\n .dg .c .slider:hover {\n background: #3c3c3c; }\n .dg .c .slider:hover .slider-fg {\n background: #44abda; }\n", 2858 | dat.controllers.factory = (function (OptionController, NumberControllerBox, NumberControllerSlider, StringController, FunctionController, BooleanController, common) { 2859 | 2860 | return function(object, property) { 2861 | 2862 | var initialValue = object[property]; 2863 | 2864 | // Providing options? 2865 | if (common.isArray(arguments[2]) || common.isObject(arguments[2])) { 2866 | return new OptionController(object, property, arguments[2]); 2867 | } 2868 | 2869 | // Providing a map? 2870 | 2871 | if (common.isNumber(initialValue)) { 2872 | 2873 | if (common.isNumber(arguments[2]) && common.isNumber(arguments[3])) { 2874 | 2875 | // Has min and max. 2876 | return new NumberControllerSlider(object, property, arguments[2], arguments[3]); 2877 | 2878 | } else { 2879 | 2880 | return new NumberControllerBox(object, property, { min: arguments[2], max: arguments[3] }); 2881 | 2882 | } 2883 | 2884 | } 2885 | 2886 | if (common.isString(initialValue)) { 2887 | return new StringController(object, property); 2888 | } 2889 | 2890 | if (common.isFunction(initialValue)) { 2891 | return new FunctionController(object, property, ''); 2892 | } 2893 | 2894 | if (common.isBoolean(initialValue)) { 2895 | return new BooleanController(object, property); 2896 | } 2897 | 2898 | } 2899 | 2900 | })(dat.controllers.OptionController, 2901 | dat.controllers.NumberControllerBox, 2902 | dat.controllers.NumberControllerSlider, 2903 | dat.controllers.StringController = (function (Controller, dom, common) { 2904 | 2905 | /** 2906 | * @class Provides a text input to alter the string property of an object. 2907 | * 2908 | * @extends dat.controllers.Controller 2909 | * 2910 | * @param {Object} object The object to be manipulated 2911 | * @param {string} property The name of the property to be manipulated 2912 | * 2913 | * @member dat.controllers 2914 | */ 2915 | var StringController = function(object, property) { 2916 | 2917 | StringController.superclass.call(this, object, property); 2918 | 2919 | var _this = this; 2920 | 2921 | this.__input = document.createElement('input'); 2922 | this.__input.setAttribute('type', 'text'); 2923 | 2924 | dom.bind(this.__input, 'keyup', onChange); 2925 | dom.bind(this.__input, 'change', onChange); 2926 | dom.bind(this.__input, 'blur', onBlur); 2927 | dom.bind(this.__input, 'keydown', function(e) { 2928 | if (e.keyCode === 13) { 2929 | this.blur(); 2930 | } 2931 | }); 2932 | 2933 | 2934 | function onChange() { 2935 | _this.setValue(_this.__input.value); 2936 | } 2937 | 2938 | function onBlur() { 2939 | if (_this.__onFinishChange) { 2940 | _this.__onFinishChange.call(_this, _this.getValue()); 2941 | } 2942 | } 2943 | 2944 | this.updateDisplay(); 2945 | 2946 | this.domElement.appendChild(this.__input); 2947 | 2948 | }; 2949 | 2950 | StringController.superclass = Controller; 2951 | 2952 | common.extend( 2953 | 2954 | StringController.prototype, 2955 | Controller.prototype, 2956 | 2957 | { 2958 | 2959 | updateDisplay: function() { 2960 | // Stops the caret from moving on account of: 2961 | // keyup -> setValue -> updateDisplay 2962 | if (!dom.isActive(this.__input)) { 2963 | this.__input.value = this.getValue(); 2964 | } 2965 | return StringController.superclass.prototype.updateDisplay.call(this); 2966 | } 2967 | 2968 | } 2969 | 2970 | ); 2971 | 2972 | return StringController; 2973 | 2974 | })(dat.controllers.Controller, 2975 | dat.dom.dom, 2976 | dat.utils.common), 2977 | dat.controllers.FunctionController, 2978 | dat.controllers.BooleanController, 2979 | dat.utils.common), 2980 | dat.controllers.Controller, 2981 | dat.controllers.BooleanController, 2982 | dat.controllers.FunctionController, 2983 | dat.controllers.NumberControllerBox, 2984 | dat.controllers.NumberControllerSlider, 2985 | dat.controllers.OptionController, 2986 | dat.controllers.ColorController = (function (Controller, dom, Color, interpret, common) { 2987 | 2988 | var ColorController = function(object, property) { 2989 | 2990 | ColorController.superclass.call(this, object, property); 2991 | 2992 | this.__color = new Color(this.getValue()); 2993 | this.__temp = new Color(0); 2994 | 2995 | var _this = this; 2996 | 2997 | this.domElement = document.createElement('div'); 2998 | 2999 | dom.makeSelectable(this.domElement, false); 3000 | 3001 | this.__selector = document.createElement('div'); 3002 | this.__selector.className = 'selector'; 3003 | 3004 | this.__saturation_field = document.createElement('div'); 3005 | this.__saturation_field.className = 'saturation-field'; 3006 | 3007 | this.__field_knob = document.createElement('div'); 3008 | this.__field_knob.className = 'field-knob'; 3009 | this.__field_knob_border = '2px solid '; 3010 | 3011 | this.__hue_knob = document.createElement('div'); 3012 | this.__hue_knob.className = 'hue-knob'; 3013 | 3014 | this.__hue_field = document.createElement('div'); 3015 | this.__hue_field.className = 'hue-field'; 3016 | 3017 | this.__input = document.createElement('input'); 3018 | this.__input.type = 'text'; 3019 | this.__input_textShadow = '0 1px 1px '; 3020 | 3021 | dom.bind(this.__input, 'keydown', function(e) { 3022 | if (e.keyCode === 13) { // on enter 3023 | onBlur.call(this); 3024 | } 3025 | }); 3026 | 3027 | dom.bind(this.__input, 'blur', onBlur); 3028 | 3029 | dom.bind(this.__selector, 'mousedown', function(e) { 3030 | 3031 | dom 3032 | .addClass(this, 'drag') 3033 | .bind(window, 'mouseup', function(e) { 3034 | dom.removeClass(_this.__selector, 'drag'); 3035 | }); 3036 | 3037 | }); 3038 | 3039 | var value_field = document.createElement('div'); 3040 | 3041 | common.extend(this.__selector.style, { 3042 | width: '122px', 3043 | height: '102px', 3044 | padding: '3px', 3045 | backgroundColor: '#222', 3046 | boxShadow: '0px 1px 3px rgba(0,0,0,0.3)' 3047 | }); 3048 | 3049 | common.extend(this.__field_knob.style, { 3050 | position: 'absolute', 3051 | width: '12px', 3052 | height: '12px', 3053 | border: this.__field_knob_border + (this.__color.v < .5 ? '#fff' : '#000'), 3054 | boxShadow: '0px 1px 3px rgba(0,0,0,0.5)', 3055 | borderRadius: '12px', 3056 | zIndex: 1 3057 | }); 3058 | 3059 | common.extend(this.__hue_knob.style, { 3060 | position: 'absolute', 3061 | width: '15px', 3062 | height: '2px', 3063 | borderRight: '4px solid #fff', 3064 | zIndex: 1 3065 | }); 3066 | 3067 | common.extend(this.__saturation_field.style, { 3068 | width: '100px', 3069 | height: '100px', 3070 | border: '1px solid #555', 3071 | marginRight: '3px', 3072 | display: 'inline-block', 3073 | cursor: 'pointer' 3074 | }); 3075 | 3076 | common.extend(value_field.style, { 3077 | width: '100%', 3078 | height: '100%', 3079 | background: 'none' 3080 | }); 3081 | 3082 | linearGradient(value_field, 'top', 'rgba(0,0,0,0)', '#000'); 3083 | 3084 | common.extend(this.__hue_field.style, { 3085 | width: '15px', 3086 | height: '100px', 3087 | display: 'inline-block', 3088 | border: '1px solid #555', 3089 | cursor: 'ns-resize' 3090 | }); 3091 | 3092 | hueGradient(this.__hue_field); 3093 | 3094 | common.extend(this.__input.style, { 3095 | outline: 'none', 3096 | // width: '120px', 3097 | textAlign: 'center', 3098 | // padding: '4px', 3099 | // marginBottom: '6px', 3100 | color: '#fff', 3101 | border: 0, 3102 | fontWeight: 'bold', 3103 | textShadow: this.__input_textShadow + 'rgba(0,0,0,0.7)' 3104 | }); 3105 | 3106 | dom.bind(this.__saturation_field, 'mousedown', fieldDown); 3107 | dom.bind(this.__field_knob, 'mousedown', fieldDown); 3108 | 3109 | dom.bind(this.__hue_field, 'mousedown', function(e) { 3110 | setH(e); 3111 | dom.bind(window, 'mousemove', setH); 3112 | dom.bind(window, 'mouseup', unbindH); 3113 | }); 3114 | 3115 | function fieldDown(e) { 3116 | setSV(e); 3117 | // document.body.style.cursor = 'none'; 3118 | dom.bind(window, 'mousemove', setSV); 3119 | dom.bind(window, 'mouseup', unbindSV); 3120 | } 3121 | 3122 | function unbindSV() { 3123 | dom.unbind(window, 'mousemove', setSV); 3124 | dom.unbind(window, 'mouseup', unbindSV); 3125 | // document.body.style.cursor = 'default'; 3126 | } 3127 | 3128 | function onBlur() { 3129 | var i = interpret(this.value); 3130 | if (i !== false) { 3131 | _this.__color.__state = i; 3132 | _this.setValue(_this.__color.toOriginal()); 3133 | } else { 3134 | this.value = _this.__color.toString(); 3135 | } 3136 | } 3137 | 3138 | function unbindH() { 3139 | dom.unbind(window, 'mousemove', setH); 3140 | dom.unbind(window, 'mouseup', unbindH); 3141 | } 3142 | 3143 | this.__saturation_field.appendChild(value_field); 3144 | this.__selector.appendChild(this.__field_knob); 3145 | this.__selector.appendChild(this.__saturation_field); 3146 | this.__selector.appendChild(this.__hue_field); 3147 | this.__hue_field.appendChild(this.__hue_knob); 3148 | 3149 | this.domElement.appendChild(this.__input); 3150 | this.domElement.appendChild(this.__selector); 3151 | 3152 | this.updateDisplay(); 3153 | 3154 | function setSV(e) { 3155 | 3156 | e.preventDefault(); 3157 | 3158 | var w = dom.getWidth(_this.__saturation_field); 3159 | var o = dom.getOffset(_this.__saturation_field); 3160 | var s = (e.clientX - o.left + document.body.scrollLeft) / w; 3161 | var v = 1 - (e.clientY - o.top + document.body.scrollTop) / w; 3162 | 3163 | if (v > 1) v = 1; 3164 | else if (v < 0) v = 0; 3165 | 3166 | if (s > 1) s = 1; 3167 | else if (s < 0) s = 0; 3168 | 3169 | _this.__color.v = v; 3170 | _this.__color.s = s; 3171 | 3172 | _this.setValue(_this.__color.toOriginal()); 3173 | 3174 | 3175 | return false; 3176 | 3177 | } 3178 | 3179 | function setH(e) { 3180 | 3181 | e.preventDefault(); 3182 | 3183 | var s = dom.getHeight(_this.__hue_field); 3184 | var o = dom.getOffset(_this.__hue_field); 3185 | var h = 1 - (e.clientY - o.top + document.body.scrollTop) / s; 3186 | 3187 | if (h > 1) h = 1; 3188 | else if (h < 0) h = 0; 3189 | 3190 | _this.__color.h = h * 360; 3191 | 3192 | _this.setValue(_this.__color.toOriginal()); 3193 | 3194 | return false; 3195 | 3196 | } 3197 | 3198 | }; 3199 | 3200 | ColorController.superclass = Controller; 3201 | 3202 | common.extend( 3203 | 3204 | ColorController.prototype, 3205 | Controller.prototype, 3206 | 3207 | { 3208 | 3209 | updateDisplay: function() { 3210 | 3211 | var i = interpret(this.getValue()); 3212 | 3213 | if (i !== false) { 3214 | 3215 | var mismatch = false; 3216 | 3217 | // Check for mismatch on the interpreted value. 3218 | 3219 | common.each(Color.COMPONENTS, function(component) { 3220 | if (!common.isUndefined(i[component]) && 3221 | !common.isUndefined(this.__color.__state[component]) && 3222 | i[component] !== this.__color.__state[component]) { 3223 | mismatch = true; 3224 | return {}; // break 3225 | } 3226 | }, this); 3227 | 3228 | // If nothing diverges, we keep our previous values 3229 | // for statefulness, otherwise we recalculate fresh 3230 | if (mismatch) { 3231 | common.extend(this.__color.__state, i); 3232 | } 3233 | 3234 | } 3235 | 3236 | common.extend(this.__temp.__state, this.__color.__state); 3237 | 3238 | this.__temp.a = 1; 3239 | 3240 | var flip = (this.__color.v < .5 || this.__color.s > .5) ? 255 : 0; 3241 | var _flip = 255 - flip; 3242 | 3243 | common.extend(this.__field_knob.style, { 3244 | marginLeft: 100 * this.__color.s - 7 + 'px', 3245 | marginTop: 100 * (1 - this.__color.v) - 7 + 'px', 3246 | backgroundColor: this.__temp.toString(), 3247 | border: this.__field_knob_border + 'rgb(' + flip + ',' + flip + ',' + flip +')' 3248 | }); 3249 | 3250 | this.__hue_knob.style.marginTop = (1 - this.__color.h / 360) * 100 + 'px' 3251 | 3252 | this.__temp.s = 1; 3253 | this.__temp.v = 1; 3254 | 3255 | linearGradient(this.__saturation_field, 'left', '#fff', this.__temp.toString()); 3256 | 3257 | common.extend(this.__input.style, { 3258 | backgroundColor: this.__input.value = this.__color.toString(), 3259 | color: 'rgb(' + flip + ',' + flip + ',' + flip +')', 3260 | textShadow: this.__input_textShadow + 'rgba(' + _flip + ',' + _flip + ',' + _flip +',.7)' 3261 | }); 3262 | 3263 | } 3264 | 3265 | } 3266 | 3267 | ); 3268 | 3269 | var vendors = ['-moz-','-o-','-webkit-','-ms-','']; 3270 | 3271 | function linearGradient(elem, x, a, b) { 3272 | elem.style.background = ''; 3273 | common.each(vendors, function(vendor) { 3274 | elem.style.cssText += 'background: ' + vendor + 'linear-gradient('+x+', '+a+' 0%, ' + b + ' 100%); '; 3275 | }); 3276 | } 3277 | 3278 | function hueGradient(elem) { 3279 | elem.style.background = ''; 3280 | elem.style.cssText += 'background: -moz-linear-gradient(top, #ff0000 0%, #ff00ff 17%, #0000ff 34%, #00ffff 50%, #00ff00 67%, #ffff00 84%, #ff0000 100%);' 3281 | elem.style.cssText += 'background: -webkit-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);' 3282 | elem.style.cssText += 'background: -o-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);' 3283 | elem.style.cssText += 'background: -ms-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);' 3284 | elem.style.cssText += 'background: linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);' 3285 | } 3286 | 3287 | 3288 | return ColorController; 3289 | 3290 | })(dat.controllers.Controller, 3291 | dat.dom.dom, 3292 | dat.color.Color = (function (interpret, math, toString, common) { 3293 | 3294 | var Color = function() { 3295 | 3296 | this.__state = interpret.apply(this, arguments); 3297 | 3298 | if (this.__state === false) { 3299 | throw 'Failed to interpret color arguments'; 3300 | } 3301 | 3302 | this.__state.a = this.__state.a || 1; 3303 | 3304 | 3305 | }; 3306 | 3307 | Color.COMPONENTS = ['r','g','b','h','s','v','hex','a']; 3308 | 3309 | common.extend(Color.prototype, { 3310 | 3311 | toString: function() { 3312 | return toString(this); 3313 | }, 3314 | 3315 | toOriginal: function() { 3316 | return this.__state.conversion.write(this); 3317 | } 3318 | 3319 | }); 3320 | 3321 | defineRGBComponent(Color.prototype, 'r', 2); 3322 | defineRGBComponent(Color.prototype, 'g', 1); 3323 | defineRGBComponent(Color.prototype, 'b', 0); 3324 | 3325 | defineHSVComponent(Color.prototype, 'h'); 3326 | defineHSVComponent(Color.prototype, 's'); 3327 | defineHSVComponent(Color.prototype, 'v'); 3328 | 3329 | Object.defineProperty(Color.prototype, 'a', { 3330 | 3331 | get: function() { 3332 | return this.__state.a; 3333 | }, 3334 | 3335 | set: function(v) { 3336 | this.__state.a = v; 3337 | } 3338 | 3339 | }); 3340 | 3341 | Object.defineProperty(Color.prototype, 'hex', { 3342 | 3343 | get: function() { 3344 | 3345 | if (!this.__state.space !== 'HEX') { 3346 | this.__state.hex = math.rgb_to_hex(this.r, this.g, this.b); 3347 | } 3348 | 3349 | return this.__state.hex; 3350 | 3351 | }, 3352 | 3353 | set: function(v) { 3354 | 3355 | this.__state.space = 'HEX'; 3356 | this.__state.hex = v; 3357 | 3358 | } 3359 | 3360 | }); 3361 | 3362 | function defineRGBComponent(target, component, componentHexIndex) { 3363 | 3364 | Object.defineProperty(target, component, { 3365 | 3366 | get: function() { 3367 | 3368 | if (this.__state.space === 'RGB') { 3369 | return this.__state[component]; 3370 | } 3371 | 3372 | recalculateRGB(this, component, componentHexIndex); 3373 | 3374 | return this.__state[component]; 3375 | 3376 | }, 3377 | 3378 | set: function(v) { 3379 | 3380 | if (this.__state.space !== 'RGB') { 3381 | recalculateRGB(this, component, componentHexIndex); 3382 | this.__state.space = 'RGB'; 3383 | } 3384 | 3385 | this.__state[component] = v; 3386 | 3387 | } 3388 | 3389 | }); 3390 | 3391 | } 3392 | 3393 | function defineHSVComponent(target, component) { 3394 | 3395 | Object.defineProperty(target, component, { 3396 | 3397 | get: function() { 3398 | 3399 | if (this.__state.space === 'HSV') 3400 | return this.__state[component]; 3401 | 3402 | recalculateHSV(this); 3403 | 3404 | return this.__state[component]; 3405 | 3406 | }, 3407 | 3408 | set: function(v) { 3409 | 3410 | if (this.__state.space !== 'HSV') { 3411 | recalculateHSV(this); 3412 | this.__state.space = 'HSV'; 3413 | } 3414 | 3415 | this.__state[component] = v; 3416 | 3417 | } 3418 | 3419 | }); 3420 | 3421 | } 3422 | 3423 | function recalculateRGB(color, component, componentHexIndex) { 3424 | 3425 | if (color.__state.space === 'HEX') { 3426 | 3427 | color.__state[component] = math.component_from_hex(color.__state.hex, componentHexIndex); 3428 | 3429 | } else if (color.__state.space === 'HSV') { 3430 | 3431 | common.extend(color.__state, math.hsv_to_rgb(color.__state.h, color.__state.s, color.__state.v)); 3432 | 3433 | } else { 3434 | 3435 | throw 'Corrupted color state'; 3436 | 3437 | } 3438 | 3439 | } 3440 | 3441 | function recalculateHSV(color) { 3442 | 3443 | var result = math.rgb_to_hsv(color.r, color.g, color.b); 3444 | 3445 | common.extend(color.__state, 3446 | { 3447 | s: result.s, 3448 | v: result.v 3449 | } 3450 | ); 3451 | 3452 | if (!common.isNaN(result.h)) { 3453 | color.__state.h = result.h; 3454 | } else if (common.isUndefined(color.__state.h)) { 3455 | color.__state.h = 0; 3456 | } 3457 | 3458 | } 3459 | 3460 | return Color; 3461 | 3462 | })(dat.color.interpret, 3463 | dat.color.math = (function () { 3464 | 3465 | var tmpComponent; 3466 | 3467 | return { 3468 | 3469 | hsv_to_rgb: function(h, s, v) { 3470 | 3471 | var hi = Math.floor(h / 60) % 6; 3472 | 3473 | var f = h / 60 - Math.floor(h / 60); 3474 | var p = v * (1.0 - s); 3475 | var q = v * (1.0 - (f * s)); 3476 | var t = v * (1.0 - ((1.0 - f) * s)); 3477 | var c = [ 3478 | [v, t, p], 3479 | [q, v, p], 3480 | [p, v, t], 3481 | [p, q, v], 3482 | [t, p, v], 3483 | [v, p, q] 3484 | ][hi]; 3485 | 3486 | return { 3487 | r: c[0] * 255, 3488 | g: c[1] * 255, 3489 | b: c[2] * 255 3490 | }; 3491 | 3492 | }, 3493 | 3494 | rgb_to_hsv: function(r, g, b) { 3495 | 3496 | var min = Math.min(r, g, b), 3497 | max = Math.max(r, g, b), 3498 | delta = max - min, 3499 | h, s; 3500 | 3501 | if (max != 0) { 3502 | s = delta / max; 3503 | } else { 3504 | return { 3505 | h: NaN, 3506 | s: 0, 3507 | v: 0 3508 | }; 3509 | } 3510 | 3511 | if (r == max) { 3512 | h = (g - b) / delta; 3513 | } else if (g == max) { 3514 | h = 2 + (b - r) / delta; 3515 | } else { 3516 | h = 4 + (r - g) / delta; 3517 | } 3518 | h /= 6; 3519 | if (h < 0) { 3520 | h += 1; 3521 | } 3522 | 3523 | return { 3524 | h: h * 360, 3525 | s: s, 3526 | v: max / 255 3527 | }; 3528 | }, 3529 | 3530 | rgb_to_hex: function(r, g, b) { 3531 | var hex = this.hex_with_component(0, 2, r); 3532 | hex = this.hex_with_component(hex, 1, g); 3533 | hex = this.hex_with_component(hex, 0, b); 3534 | return hex; 3535 | }, 3536 | 3537 | component_from_hex: function(hex, componentIndex) { 3538 | return (hex >> (componentIndex * 8)) & 0xFF; 3539 | }, 3540 | 3541 | hex_with_component: function(hex, componentIndex, value) { 3542 | return value << (tmpComponent = componentIndex * 8) | (hex & ~ (0xFF << tmpComponent)); 3543 | } 3544 | 3545 | } 3546 | 3547 | })(), 3548 | dat.color.toString, 3549 | dat.utils.common), 3550 | dat.color.interpret, 3551 | dat.utils.common), 3552 | dat.utils.requestAnimationFrame = (function () { 3553 | 3554 | /** 3555 | * requirejs version of Paul Irish's RequestAnimationFrame 3556 | * http://paulirish.com/2011/requestanimationframe-for-smart-animating/ 3557 | */ 3558 | 3559 | return window.requestAnimationFrame || 3560 | window.webkitRequestAnimationFrame || 3561 | window.mozRequestAnimationFrame || 3562 | window.oRequestAnimationFrame || 3563 | window.msRequestAnimationFrame || 3564 | function(callback, element) { 3565 | 3566 | window.setTimeout(callback, 1000 / 60); 3567 | 3568 | }; 3569 | })(), 3570 | dat.dom.CenteredDiv = (function (dom, common) { 3571 | 3572 | 3573 | var CenteredDiv = function() { 3574 | 3575 | this.backgroundElement = document.createElement('div'); 3576 | common.extend(this.backgroundElement.style, { 3577 | backgroundColor: 'rgba(0,0,0,0.8)', 3578 | top: 0, 3579 | left: 0, 3580 | display: 'none', 3581 | zIndex: '1000', 3582 | opacity: 0, 3583 | WebkitTransition: 'opacity 0.2s linear', 3584 | transition: 'opacity 0.2s linear' 3585 | }); 3586 | 3587 | dom.makeFullscreen(this.backgroundElement); 3588 | this.backgroundElement.style.position = 'fixed'; 3589 | 3590 | this.domElement = document.createElement('div'); 3591 | common.extend(this.domElement.style, { 3592 | position: 'fixed', 3593 | display: 'none', 3594 | zIndex: '1001', 3595 | opacity: 0, 3596 | WebkitTransition: '-webkit-transform 0.2s ease-out, opacity 0.2s linear', 3597 | transition: 'transform 0.2s ease-out, opacity 0.2s linear' 3598 | }); 3599 | 3600 | 3601 | document.body.appendChild(this.backgroundElement); 3602 | document.body.appendChild(this.domElement); 3603 | 3604 | var _this = this; 3605 | dom.bind(this.backgroundElement, 'click', function() { 3606 | _this.hide(); 3607 | }); 3608 | 3609 | 3610 | }; 3611 | 3612 | CenteredDiv.prototype.show = function() { 3613 | 3614 | var _this = this; 3615 | 3616 | this.backgroundElement.style.display = 'block'; 3617 | 3618 | this.domElement.style.display = 'block'; 3619 | this.domElement.style.opacity = 0; 3620 | // this.domElement.style.top = '52%'; 3621 | this.domElement.style.webkitTransform = 'scale(1.1)'; 3622 | 3623 | this.layout(); 3624 | 3625 | common.defer(function() { 3626 | _this.backgroundElement.style.opacity = 1; 3627 | _this.domElement.style.opacity = 1; 3628 | _this.domElement.style.webkitTransform = 'scale(1)'; 3629 | }); 3630 | 3631 | }; 3632 | 3633 | CenteredDiv.prototype.hide = function() { 3634 | 3635 | var _this = this; 3636 | 3637 | var hide = function() { 3638 | 3639 | _this.domElement.style.display = 'none'; 3640 | _this.backgroundElement.style.display = 'none'; 3641 | 3642 | dom.unbind(_this.domElement, 'webkitTransitionEnd', hide); 3643 | dom.unbind(_this.domElement, 'transitionend', hide); 3644 | dom.unbind(_this.domElement, 'oTransitionEnd', hide); 3645 | 3646 | }; 3647 | 3648 | dom.bind(this.domElement, 'webkitTransitionEnd', hide); 3649 | dom.bind(this.domElement, 'transitionend', hide); 3650 | dom.bind(this.domElement, 'oTransitionEnd', hide); 3651 | 3652 | this.backgroundElement.style.opacity = 0; 3653 | // this.domElement.style.top = '48%'; 3654 | this.domElement.style.opacity = 0; 3655 | this.domElement.style.webkitTransform = 'scale(1.1)'; 3656 | 3657 | }; 3658 | 3659 | CenteredDiv.prototype.layout = function() { 3660 | this.domElement.style.left = window.innerWidth/2 - dom.getWidth(this.domElement) / 2 + 'px'; 3661 | this.domElement.style.top = window.innerHeight/2 - dom.getHeight(this.domElement) / 2 + 'px'; 3662 | }; 3663 | 3664 | function lockScroll(e) { 3665 | console.log(e); 3666 | } 3667 | 3668 | return CenteredDiv; 3669 | 3670 | })(dat.dom.dom, 3671 | dat.utils.common), 3672 | dat.dom.dom, 3673 | dat.utils.common); --------------------------------------------------------------------------------