From babc6fce6b6d9489c9507aedfc907caa8b625178 Mon Sep 17 00:00:00 2001 From: FabrizioC Date: Tue, 26 Jun 2012 19:27:12 +0200 Subject: [PATCH 01/12] added option 'angleAperture' || default = 360 'angleAperture' define maximum angle of knob (in degree) --- .DS_Store | Bin 0 -> 6148 bytes js/jquery.knob.js | 5 +++-- 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..ba3b77f6a8c3248a9cea646d67980a85d45fcca5 GIT binary patch literal 6148 zcmeH~Jr2S!425lAKw|00n1usyg9yP1xBvnsRH*}E&(V4QVW2Rh3O!5q7rU|AzM-i_ zM0bztR-_Y=CEO?*3nNqHGr7tAdcXANVd$-pHcMIw-pT0tYadAkr~nn90#twsOh Date: Wed, 27 Jun 2012 10:10:21 +0200 Subject: [PATCH 02/12] Revert "added option 'angleAperture' || default = 360" This reverts commit babc6fce6b6d9489c9507aedfc907caa8b625178. --- .DS_Store | Bin 6148 -> 0 bytes js/jquery.knob.js | 5 ++--- 2 files changed, 2 insertions(+), 3 deletions(-) delete mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index ba3b77f6a8c3248a9cea646d67980a85d45fcca5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeH~Jr2S!425lAKw|00n1usyg9yP1xBvnsRH*}E&(V4QVW2Rh3O!5q7rU|AzM-i_ zM0bztR-_Y=CEO?*3nNqHGr7tAdcXANVd$-pHcMIw-pT0tYadAkr~nn90#twsOh Date: Wed, 27 Jun 2012 10:10:28 +0200 Subject: [PATCH 03/12] Revert "Revert "added option 'angleAperture' || default = 360"" This reverts commit 4bf72f722b290cbd2365fa35cc1e28f7ac102d9e. --- .DS_Store | Bin 0 -> 6148 bytes js/jquery.knob.js | 5 +++-- 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..ba3b77f6a8c3248a9cea646d67980a85d45fcca5 GIT binary patch literal 6148 zcmeH~Jr2S!425lAKw|00n1usyg9yP1xBvnsRH*}E&(V4QVW2Rh3O!5q7rU|AzM-i_ zM0bztR-_Y=CEO?*3nNqHGr7tAdcXANVd$-pHcMIw-pT0tYadAkr~nn90#twsOh Date: Wed, 27 Jun 2012 10:10:30 +0200 Subject: [PATCH 04/12] Revert "Revert "Revert "added option 'angleAperture' || default = 360""" This reverts commit 8f40afde8cc0682e2b9feb6919021fb59ebe09db. --- .DS_Store | Bin 6148 -> 0 bytes js/jquery.knob.js | 5 ++--- 2 files changed, 2 insertions(+), 3 deletions(-) delete mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index ba3b77f6a8c3248a9cea646d67980a85d45fcca5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeH~Jr2S!425lAKw|00n1usyg9yP1xBvnsRH*}E&(V4QVW2Rh3O!5q7rU|AzM-i_ zM0bztR-_Y=CEO?*3nNqHGr7tAdcXANVd$-pHcMIw-pT0tYadAkr~nn90#twsOh Date: Wed, 27 Jun 2012 10:16:52 +0200 Subject: [PATCH 05/12] added option 'angleArc' || default = 360 'angleArc' define the angle of knob (in degree) --- js/jquery.knob.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/js/jquery.knob.js b/js/jquery.knob.js index 39fdac8..00e1b69 100644 --- a/js/jquery.knob.js +++ b/js/jquery.knob.js @@ -49,7 +49,7 @@ $(function () { }; this.angle = function (nv) { - return (nv - opt.min) * PI2 / (opt.max - opt.min); + return (nv - opt.min) * opt.angleArc / (opt.max - opt.min); }; this.draw = function (nv) { @@ -242,7 +242,8 @@ $(function () { ,'tickWidth' : $this.data('tickWidth') || 0.02 ,'tickColorizeValues' : $this.data('tickColorizeValues') || true ,'skin' : $this.data('skin') || 'default' - ,'angleOffset': degreeToRadians($this.data('angleoffset')) + ,'angleOffset': degreeToRadians($this.data('angleoffset')) + ,'angleArc': degreeToRadians($this.data('anglearc')) || 2 * Math.PI // Hooks ,'draw' : From f5878f8ce68e443f6c3947ff2859b278605fc559 Mon Sep 17 00:00:00 2001 From: FabrizioC Date: Wed, 27 Jun 2012 10:29:21 +0200 Subject: [PATCH 06/12] Added angleArc example --- index.html | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/index.html b/index.html index c55d500..ed88799 100644 --- a/index.html +++ b/index.html @@ -130,6 +130,18 @@ +
+
+data-width="200"
+data-min="0"
+data-max="100"
+data-cgColor="#A9EFFD"
+data-displayPrevious=true
+data-angleOffset="-135"
+data-angleArc="270"
+            
+ +

Ticks

From 2734c2733a30bff709095c257ff314e9cac2c57e Mon Sep 17 00:00:00 2001 From: FabrizioC Date: Wed, 27 Jun 2012 10:35:21 +0200 Subject: [PATCH 07/12] added 'angleArc' description --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b79a9b5..18c32d6 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ Behaviors : * stopper : stop at 0 & 100 on keydown/mousewheel || default=true. * readOnly : disable input and events. * angleOffset: change the 0 position of the knob (in degree), || default=0. +* angleArc: change the angle of the knob (in degree), || default=360. UI : * cursor : display mode "cursor" | default=gauge. From 3cc11253dda6d022ba17d89e044bf5ece4f32725 Mon Sep 17 00:00:00 2001 From: FabrizioC Date: Wed, 27 Jun 2012 17:10:59 +0200 Subject: [PATCH 08/12] added option 'angleBgCircle' || default = false 'angleBgCircle' show a full circle background when 'angleArc' is less than 360 degrees. --- js/jquery.knob.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/js/jquery.knob.js b/js/jquery.knob.js index 00e1b69..a221cd1 100644 --- a/js/jquery.knob.js +++ b/js/jquery.knob.js @@ -36,8 +36,10 @@ $(function () { } else { var b, a; b = a = Math.atan2(mx - x, -(my - y - opt.width / 2)) - opt.angleOffset; - (a < 0) && (b = a + PI2); - nv = Math.round(b * (opt.max - opt.min) / PI2) + opt.min; +// (a < 0) && (b = a + PI2); + (a < 0) && (b = a + opt.angleArc); +// nv = Math.round(b * (opt.max - opt.min) / PI2) + opt.min; + nv = Math.round(b * (opt.max - opt.min) / opt.angleArc) + opt.min; return (nv > opt.max) ? opt.max : nv; } }; @@ -63,6 +65,8 @@ $(function () { ,lw = r * opt.thickness // Line width ,cgcolor = Dial.getCgColor(opt.cgColor) ,tick + ,bsa = sa + ,bea = bsa + (opt.angleBgCircle ? (2 * Math.PI) : opt.angleArc) ; ctx.clearRect(0, 0, opt.width, opt.width); @@ -98,7 +102,7 @@ $(function () { ctx.beginPath(); ctx.strokeStyle = opt.bgColor; - ctx.arc(r, r, r - lw / 2, 0, PI2, true); + ctx.arc(r, r, r - lw / 2, bea, bsa, true); ctx.stroke(); if (opt.displayPrevious) { @@ -244,6 +248,8 @@ $(function () { ,'skin' : $this.data('skin') || 'default' ,'angleOffset': degreeToRadians($this.data('angleoffset')) ,'angleArc': degreeToRadians($this.data('anglearc')) || 2 * Math.PI + ,'angleBgCircle': $this.data('anglebgcircle') || false + // Hooks ,'draw' : From 66eee6d945bde2eff7082ceb63e9f67b6514c5f6 Mon Sep 17 00:00:00 2001 From: FabrizioC Date: Thu, 11 Dec 2014 18:32:45 +0100 Subject: [PATCH 09/12] Update from original --- LICENSE | 20 + README.md | 150 +++--- bower.json | 13 + dist/jquery.knob.min.js | 1 + excanvas.js | 924 ++++++++++++++++++++++++++++++++ index.html | 361 ++++++++----- js/jquery.knob.js | 1105 ++++++++++++++++++++++++++------------- knob.jquery.json | 37 ++ secretplan.jpg | Bin 0 -> 33487 bytes 9 files changed, 2040 insertions(+), 571 deletions(-) create mode 100755 LICENSE mode change 100644 => 100755 README.md create mode 100755 bower.json create mode 100755 dist/jquery.knob.min.js create mode 100755 excanvas.js mode change 100644 => 100755 index.html create mode 100755 knob.jquery.json create mode 100755 secretplan.jpg diff --git a/LICENSE b/LICENSE new file mode 100755 index 0000000..f5db321 --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013 Anthony Terrien + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md old mode 100644 new mode 100755 index 18c32d6..d20c58a --- a/README.md +++ b/README.md @@ -2,73 +2,80 @@ jQuery Knob ============= - canvas based ; no png or jpg sprites. -- touch, mousewheel, keyboard events implemented. +- touch, mouse and mousewheel, keyboard events implemented. - downward compatible ; overloads an input element. Example ------- - +```html + - + }); + +``` Options ------- Options are provided as attributes 'data-option': - +```html + +``` ... or in the "knob()" call : - $(".dial").knob({ - 'min':-50 - ,'max':50 - }) +```javascript +$(".dial").knob({ + 'min':-50, + 'max':50 +}); +``` The following options are supported : Behaviors : -* min : min value || default=0. -* max : max value || default=100. -* stopper : stop at 0 & 100 on keydown/mousewheel || default=true. -* readOnly : disable input and events. -* angleOffset: change the 0 position of the knob (in degree), || default=0. -* angleArc: change the angle of the knob (in degree), || default=360. +* min : min value | default=0. +* max : max value | default=100. +* step : step size | default=1. +* angleOffset : starting angle in degrees | default=0. +* angleArc : arc size in degrees | default=360. +* stopper : stop at min & max on keydown/mousewheel | default=true. +* readOnly : disable input and events | default=false. +* rotation : direction of progression | default=clockwise. UI : -* cursor : display mode "cursor" | default=gauge. +* cursor : display mode "cursor", cursor size could be changed passing a numeric value to the option, default width is used when passing boolean value "true" | default=gauge. * thickness : gauge thickness. +* lineCap : gauge stroke endings. | default=butt, round=rounded line endings * width : dial width. * displayInput : default=true | false=hide input. * displayPrevious : default=false | true=displays the previous value with transparency. * fgColor : foreground color. +* inputColor : input value (number) color. +* font : font family. +* fontWeight : font weight. * bgColor : background color. -* ticks : number of ticks | 0=disable. -* tickColor. -* tickLength. -* tickWidth. -* tickColorizeValues : colorize ticks. -* skin : default | "tron". Hooks ------- - + 'release' : function (v) { /*make something*/ } + }); + +``` * 'release' : executed on release Parameters : + value : int, current value - + input : jQuery element, input element * 'change' : executed at each change of the value @@ -77,57 +84,64 @@ Hooks * 'draw' : when drawing the canvas - Parameters : - + angle : computed angle - + value : current value - + opt : plugin options - + context : Canvas context 2d + Context : + - this.g : canvas context 2D (see Canvas documentation) + - this.$ : jQuery wrapped element + - this.o : options + - this.i : input + - ... console.log(this); + +* 'cancel' : triggered on [esc] keydown + +* 'format' : allows to format output (add unit %, ms ...) + +The scope (this) of each hook function is the current Knob instance (refer to the demo code). Example ------- - - - +```html + + +``` Dynamically configure ------- - - -Supported browser +```html + +``` + +Set the value ------- -Tested on Chrome, Safari, Firefox, IE 9.0. +```html + +``` -Revision 1.1.2 -------- -- removed padding around the wheel in default skin / default thickness = 0.35. -- 'configure' event. -- added 'displayPrevious' option. -- change color cgColor / 'displayPrevious' must be true. -- escape keycode supported while changing = restore the current value. -- tab keycode supported. -- added 'stopper'. -- bugfix init when 'value' attribute is not defined. -- JSLint qa. -- infinite mode demo. - -Revision 1.1.1 +Supported browser ------- -- keyboard/mousewheel control refactoring / acceleration. -- bugfix no keyboard or mousewheel when readonly. -- bugfix min/max can be exceeded. -- hooks / keyboard events. +Tested on Chrome, Safari, Firefox, IE>=8.0 (IE8.0 with excanvas). + +![secretplan](https://raw.github.com/aterrien/jQuery-Knob/master/secretplan.jpg) diff --git a/bower.json b/bower.json new file mode 100755 index 0000000..7969769 --- /dev/null +++ b/bower.json @@ -0,0 +1,13 @@ +{ + "name": "aterrien/jQuery-Knob", + "version": "1.2.11", + "main": "js/jquery.knob.js", + "description": "Nice, downward compatible, touchable, jQuery dial.", + "license": "MIT", + "ignore": [], + "dependencies": { + "jquery": ">=1.7.0" + }, + "devDependencies": {} +} + diff --git a/dist/jquery.knob.min.js b/dist/jquery.knob.min.js new file mode 100755 index 0000000..72d6ed2 --- /dev/null +++ b/dist/jquery.knob.min.js @@ -0,0 +1 @@ +(function(e){if(typeof define==="function"&&define.amd){define(["jquery"],e)}else{e(jQuery)}})(function(e){"use strict";var t={},n=Math.max,r=Math.min;t.c={};t.c.d=e(document);t.c.t=function(e){return e.originalEvent.touches.length-1};t.o=function(){var n=this;this.o=null;this.$=null;this.i=null;this.g=null;this.v=null;this.cv=null;this.x=0;this.y=0;this.w=0;this.h=0;this.$c=null;this.c=null;this.t=0;this.isInit=false;this.fgColor=null;this.pColor=null;this.dH=null;this.cH=null;this.eH=null;this.rH=null;this.scale=1;this.relative=false;this.relativeWidth=false;this.relativeHeight=false;this.$div=null;this.run=function(){var t=function(e,t){var r;for(r in t){n.o[r]=t[r]}n._carve().init();n._configure()._draw()};if(this.$.data("kontroled"))return;this.$.data("kontroled",true);this.extend();this.o=e.extend({min:this.$.data("min")!==undefined?this.$.data("min"):0,max:this.$.data("max")!==undefined?this.$.data("max"):100,stopper:true,readOnly:this.$.data("readonly")||this.$.attr("readonly")==="readonly",cursor:this.$.data("cursor")===true&&30||this.$.data("cursor")||0,thickness:this.$.data("thickness")&&Math.max(Math.min(this.$.data("thickness"),1),.01)||.35,lineCap:this.$.data("linecap")||"butt",width:this.$.data("width")||200,height:this.$.data("height")||200,displayInput:this.$.data("displayinput")==null||this.$.data("displayinput"),displayPrevious:this.$.data("displayprevious"),fgColor:this.$.data("fgcolor")||"#87CEEB",inputColor:this.$.data("inputcolor"),font:this.$.data("font")||"Arial",fontWeight:this.$.data("font-weight")||"bold",inline:false,step:this.$.data("step")||1,rotation:this.$.data("rotation"),draw:null,change:null,cancel:null,release:null,format:function(e){return e},parse:function(e){return parseFloat(e)}},this.o);this.o.flip=this.o.rotation==="anticlockwise"||this.o.rotation==="acw";if(!this.o.inputColor){this.o.inputColor=this.o.fgColor}if(this.$.is("fieldset")){this.v={};this.i=this.$.find("input");this.i.each(function(t){var r=e(this);n.i[t]=r;n.v[t]=n.o.parse(r.val());r.bind("change blur",function(){var e={};e[t]=r.val();n.val(n._validate(e))})});this.$.find("legend").remove()}else{this.i=this.$;this.v=this.o.parse(this.$.val());this.v===""&&(this.v=this.o.min);this.$.bind("change blur",function(){n.val(n._validate(n.o.parse(n.$.val())))})}!this.o.displayInput&&this.$.hide();this.$c=e(document.createElement("canvas")).attr({width:this.o.width,height:this.o.height});this.$div=e('
');this.$.wrap(this.$div).before(this.$c);this.$div=this.$.parent();if(typeof G_vmlCanvasManager!=="undefined"){G_vmlCanvasManager.initElement(this.$c[0])}this.c=this.$c[0].getContext?this.$c[0].getContext("2d"):null;if(!this.c){throw{name:"CanvasNotSupportedException",message:"Canvas not supported. Please use excanvas on IE8.0.",toString:function(){return this.name+": "+this.message}}}this.scale=(window.devicePixelRatio||1)/(this.c.webkitBackingStorePixelRatio||this.c.mozBackingStorePixelRatio||this.c.msBackingStorePixelRatio||this.c.oBackingStorePixelRatio||this.c.backingStorePixelRatio||1);this.relativeWidth=this.o.width%1!==0&&this.o.width.indexOf("%");this.relativeHeight=this.o.height%1!==0&&this.o.height.indexOf("%");this.relative=this.relativeWidth||this.relativeHeight;this._carve();if(this.v instanceof Object){this.cv={};this.copy(this.v,this.cv)}else{this.cv=this.v}this.$.bind("configure",t).parent().bind("configure",t);this._listen()._configure()._xy().init();this.isInit=true;this.$.val(this.o.format(this.v));this._draw();return this};this._carve=function(){if(this.relative){var e=this.relativeWidth?this.$div.parent().width()*parseInt(this.o.width)/100:this.$div.parent().width(),t=this.relativeHeight?this.$div.parent().height()*parseInt(this.o.height)/100:this.$div.parent().height();this.w=this.h=Math.min(e,t)}else{this.w=this.o.width;this.h=this.o.height}this.$div.css({width:this.w+"px",height:this.h+"px"});this.$c.attr({width:this.w,height:this.h});if(this.scale!==1){this.$c[0].width=this.$c[0].width*this.scale;this.$c[0].height=this.$c[0].height*this.scale;this.$c.width(this.w);this.$c.height(this.h)}return this};this._draw=function(){var e=true;n.g=n.c;n.clear();n.dH&&(e=n.dH());e!==false&&n.draw()};this._touch=function(e){var r=function(e){var t=n.xy2val(e.originalEvent.touches[n.t].pageX,e.originalEvent.touches[n.t].pageY);if(t==n.cv)return;if(n.cH&&n.cH(t)===false)return;n.change(n._validate(t));n._draw()};this.t=t.c.t(e);r(e);t.c.d.bind("touchmove.k",r).bind("touchend.k",function(){t.c.d.unbind("touchmove.k touchend.k");n.val(n.cv)});return this};this._mouse=function(e){var r=function(e){var t=n.xy2val(e.pageX,e.pageY);if(t==n.cv)return;if(n.cH&&n.cH(t)===false)return;n.change(n._validate(t));n._draw()};r(e);t.c.d.bind("mousemove.k",r).bind("keyup.k",function(e){if(e.keyCode===27){t.c.d.unbind("mouseup.k mousemove.k keyup.k");if(n.eH&&n.eH()===false)return;n.cancel()}}).bind("mouseup.k",function(e){t.c.d.unbind("mousemove.k mouseup.k keyup.k");n.val(n.cv)});return this};this._xy=function(){var e=this.$c.offset();this.x=e.left;this.y=e.top;return this};this._listen=function(){if(!this.o.readOnly){this.$c.bind("mousedown",function(e){e.preventDefault();n._xy()._mouse(e)}).bind("touchstart",function(e){e.preventDefault();n._xy()._touch(e)});this.listen()}else{this.$.attr("readonly","readonly")}if(this.relative){e(window).resize(function(){n._carve().init();n._draw()})}return this};this._configure=function(){if(this.o.draw)this.dH=this.o.draw;if(this.o.change)this.cH=this.o.change;if(this.o.cancel)this.eH=this.o.cancel;if(this.o.release)this.rH=this.o.release;if(this.o.displayPrevious){this.pColor=this.h2rgba(this.o.fgColor,"0.4");this.fgColor=this.h2rgba(this.o.fgColor,"0.6")}else{this.fgColor=this.o.fgColor}return this};this._clear=function(){this.$c[0].width=this.$c[0].width};this._validate=function(e){var t=~~((e<0?-.5:.5)+e/this.o.step)*this.o.step;return Math.round(t*100)/100};this.listen=function(){};this.extend=function(){};this.init=function(){};this.change=function(e){};this.val=function(e){};this.xy2val=function(e,t){};this.draw=function(){};this.clear=function(){this._clear()};this.h2rgba=function(e,t){var n;e=e.substring(1,7);n=[parseInt(e.substring(0,2),16),parseInt(e.substring(2,4),16),parseInt(e.substring(4,6),16)];return"rgba("+n[0]+","+n[1]+","+n[2]+","+t+")"};this.copy=function(e,t){for(var n in e){t[n]=e[n]}}};t.Dial=function(){t.o.call(this);this.startAngle=null;this.xy=null;this.radius=null;this.lineWidth=null;this.cursorExt=null;this.w2=null;this.PI2=2*Math.PI;this.extend=function(){this.o=e.extend({bgColor:this.$.data("bgcolor")||"#EEEEEE",angleOffset:this.$.data("angleoffset")||0,angleArc:this.$.data("anglearc")||360,inline:true},this.o)};this.val=function(e,t){if(null!=e){e=this.o.parse(e);if(t!==false&&e!=this.v&&this.rH&&this.rH(e)===false){return}this.cv=this.o.stopper?n(r(e,this.o.max),this.o.min):e;this.v=this.cv;this.$.val(this.o.format(this.v));this._draw()}else{return this.v}};this.xy2val=function(e,t){var i,s;i=Math.atan2(e-(this.x+this.w2),-(t-this.y-this.w2))-this.angleOffset;if(this.o.flip){i=this.angleArc-i-this.PI2}if(this.angleArc!=this.PI2&&i<0&&i>-.5){i=0}else if(i<0){i+=this.PI2}s=i*(this.o.max-this.o.min)/this.angleArc+this.o.min;this.o.stopper&&(s=n(r(s,this.o.max),this.o.min));return s};this.listen=function(){var t=this,i,s,o=function(e){e.preventDefault();var o=e.originalEvent,u=o.detail||o.wheelDeltaX,a=o.detail||o.wheelDeltaY,f=t._validate(t.o.parse(t.$.val()))+(u>0||a>0?t.o.step:u<0||a<0?-t.o.step:0);f=n(r(f,t.o.max),t.o.min);t.val(f,false);if(t.rH){clearTimeout(i);i=setTimeout(function(){t.rH(f);i=null},100);if(!s){s=setTimeout(function(){if(i)t.rH(f);s=null},200)}}},u,a,f=1,l={37:-t.o.step,38:t.o.step,39:t.o.step,40:-t.o.step};this.$.bind("keydown",function(i){var s=i.keyCode;if(s>=96&&s<=105){s=i.keyCode=s-48}u=parseInt(String.fromCharCode(s));if(isNaN(u)){s!==13&&s!==8&&s!==9&&s!==189&&(s!==190||t.$.val().match(/\./))&&i.preventDefault();if(e.inArray(s,[37,38,39,40])>-1){i.preventDefault();var o=t.o.parse(t.$.val())+l[s]*f;t.o.stopper&&(o=n(r(o,t.o.max),t.o.min));t.change(t._validate(o));t._draw();a=window.setTimeout(function(){f*=2},30)}}}).bind("keyup",function(e){if(isNaN(u)){if(a){window.clearTimeout(a);a=null;f=1;t.val(t.$.val())}}else{t.$.val()>t.o.max&&t.$.val(t.o.max)||t.$.val()this.o.max){this.v=this.o.min}this.$.val(this.v);this.w2=this.w/2;this.cursorExt=this.o.cursor/100;this.xy=this.w2*this.scale;this.lineWidth=this.xy*this.o.thickness;this.lineCap=this.o.lineCap;this.radius=this.xy-this.lineWidth/2;this.o.angleOffset&&(this.o.angleOffset=isNaN(this.o.angleOffset)?0:this.o.angleOffset);this.o.angleArc&&(this.o.angleArc=isNaN(this.o.angleArc)?this.PI2:this.o.angleArc);this.angleOffset=this.o.angleOffset*Math.PI/180;this.angleArc=this.o.angleArc*Math.PI/180;this.startAngle=1.5*Math.PI+this.angleOffset;this.endAngle=1.5*Math.PI+this.angleOffset+this.angleArc;var e=n(String(Math.abs(this.o.max)).length,String(Math.abs(this.o.min)).length,2)+2;this.o.displayInput&&this.i.css({width:(this.w/2+4>>0)+"px",height:(this.w/3>>0)+"px",position:"absolute","vertical-align":"middle","margin-top":(this.w/3>>0)+"px","margin-left":"-"+(this.w*3/4+2>>0)+"px",border:0,background:"none",font:this.o.fontWeight+" "+(this.w/e>>0)+"px "+this.o.font,"text-align":"center",color:this.o.inputColor||this.o.fgColor,padding:"0px","-webkit-appearance":"none"})||this.i.css({width:"0px",visibility:"hidden"})};this.change=function(e){this.cv=e;this.$.val(this.o.format(e))};this.angle=function(e){return(e-this.o.min)*this.angleArc/(this.o.max-this.o.min)};this.arc=function(e){var t,n;e=this.angle(e);if(this.o.flip){t=this.endAngle+1e-5;n=t-e-1e-5}else{t=this.startAngle-1e-5;n=t+e+1e-5}this.o.cursor&&(t=n-this.cursorExt)&&(n=n+this.cursorExt);return{s:t,e:n,d:this.o.flip&&!this.o.cursor}};this.draw=function(){var e=this.g,t=this.arc(this.cv),n,r=1;e.lineWidth=this.lineWidth;e.lineCap=this.lineCap;if(this.o.bgColor!=="none"){e.beginPath();e.strokeStyle=this.o.bgColor;e.arc(this.xy,this.xy,this.radius,this.endAngle-1e-5,this.startAngle+1e-5,true);e.stroke()}if(this.o.displayPrevious){n=this.arc(this.v);e.beginPath();e.strokeStyle=this.pColor;e.arc(this.xy,this.xy,this.radius,n.s,n.e,n.d);e.stroke();r=this.cv==this.v}e.beginPath();e.strokeStyle=r?this.o.fgColor:this.fgColor;e.arc(this.xy,this.xy,this.radius,t.s,t.e,t.d);e.stroke()};this.cancel=function(){this.val(this.v)}};e.fn.dial=e.fn.knob=function(n){return this.each(function(){var r=new t.Dial;r.o=n;r.$=e(this);r.run()}).parent()}}) \ No newline at end of file diff --git a/excanvas.js b/excanvas.js new file mode 100755 index 0000000..367764b --- /dev/null +++ b/excanvas.js @@ -0,0 +1,924 @@ +// Copyright 2006 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +// Known Issues: +// +// * Patterns are not implemented. +// * Radial gradient are not implemented. The VML version of these look very +// different from the canvas one. +// * Clipping paths are not implemented. +// * Coordsize. The width and height attribute have higher priority than the +// width and height style values which isn't correct. +// * Painting mode isn't implemented. +// * Canvas width/height should is using content-box by default. IE in +// Quirks mode will draw the canvas using border-box. Either change your +// doctype to HTML5 +// (http://www.whatwg.org/specs/web-apps/current-work/#the-doctype) +// or use Box Sizing Behavior from WebFX +// (http://webfx.eae.net/dhtml/boxsizing/boxsizing.html) +// * Non uniform scaling does not correctly scale strokes. +// * Optimize. There is always room for speed improvements. + +// Only add this code if we do not already have a canvas implementation +if (!document.createElement('canvas').getContext) { + +(function() { + + // alias some functions to make (compiled) code shorter + var m = Math; + var mr = m.round; + var ms = m.sin; + var mc = m.cos; + var abs = m.abs; + var sqrt = m.sqrt; + + // this is used for sub pixel precision + var Z = 10; + var Z2 = Z / 2; + + /** + * This funtion is assigned to the elements as element.getContext(). + * @this {HTMLElement} + * @return {CanvasRenderingContext2D_} + */ + function getContext() { + return this.context_ || + (this.context_ = new CanvasRenderingContext2D_(this)); + } + + var slice = Array.prototype.slice; + + /** + * Binds a function to an object. The returned function will always use the + * passed in {@code obj} as {@code this}. + * + * Example: + * + * g = bind(f, obj, a, b) + * g(c, d) // will do f.call(obj, a, b, c, d) + * + * @param {Function} f The function to bind the object to + * @param {Object} obj The object that should act as this when the function + * is called + * @param {*} var_args Rest arguments that will be used as the initial + * arguments when the function is called + * @return {Function} A new function that has bound this + */ + function bind(f, obj, var_args) { + var a = slice.call(arguments, 2); + return function() { + return f.apply(obj, a.concat(slice.call(arguments))); + }; + } + + var G_vmlCanvasManager_ = { + init: function(opt_doc) { + if (/MSIE/.test(navigator.userAgent) && !window.opera) { + var doc = opt_doc || document; + // Create a dummy element so that IE will allow canvas elements to be + // recognized. + doc.createElement('canvas'); + doc.attachEvent('onreadystatechange', bind(this.init_, this, doc)); + } + }, + + init_: function(doc) { + // create xmlns + if (!doc.namespaces['g_vml_']) { + doc.namespaces.add('g_vml_', 'urn:schemas-microsoft-com:vml', + '#default#VML'); + + } + if (!doc.namespaces['g_o_']) { + doc.namespaces.add('g_o_', 'urn:schemas-microsoft-com:office:office', + '#default#VML'); + } + + // Setup default CSS. Only add one style sheet per document + if (!doc.styleSheets['ex_canvas_']) { + var ss = doc.createStyleSheet(); + ss.owningElement.id = 'ex_canvas_'; + ss.cssText = 'canvas{display:inline-block;overflow:hidden;' + + // default size is 300x150 in Gecko and Opera + 'text-align:left;width:300px;height:150px}' + + 'g_vml_\\:*{behavior:url(#default#VML)}' + + 'g_o_\\:*{behavior:url(#default#VML)}'; + + } + + // find all canvas elements + var els = doc.getElementsByTagName('canvas'); + for (var i = 0; i < els.length; i++) { + this.initElement(els[i]); + } + }, + + /** + * Public initializes a canvas element so that it can be used as canvas + * element from now on. This is called automatically before the page is + * loaded but if you are creating elements using createElement you need to + * make sure this is called on the element. + * @param {HTMLElement} el The canvas element to initialize. + * @return {HTMLElement} the element that was created. + */ + initElement: function(el) { + if (!el.getContext) { + + el.getContext = getContext; + + // Remove fallback content. There is no way to hide text nodes so we + // just remove all childNodes. We could hide all elements and remove + // text nodes but who really cares about the fallback content. + el.innerHTML = ''; + + // do not use inline function because that will leak memory + el.attachEvent('onpropertychange', onPropertyChange); + el.attachEvent('onresize', onResize); + + var attrs = el.attributes; + if (attrs.width && attrs.width.specified) { + // TODO: use runtimeStyle and coordsize + // el.getContext().setWidth_(attrs.width.nodeValue); + el.style.width = attrs.width.nodeValue + 'px'; + } else { + el.width = el.clientWidth; + } + if (attrs.height && attrs.height.specified) { + // TODO: use runtimeStyle and coordsize + // el.getContext().setHeight_(attrs.height.nodeValue); + el.style.height = attrs.height.nodeValue + 'px'; + } else { + el.height = el.clientHeight; + } + //el.getContext().setCoordsize_() + } + return el; + } + }; + + function onPropertyChange(e) { + var el = e.srcElement; + + switch (e.propertyName) { + case 'width': + el.style.width = el.attributes.width.nodeValue + 'px'; + el.getContext().clearRect(); + break; + case 'height': + el.style.height = el.attributes.height.nodeValue + 'px'; + el.getContext().clearRect(); + break; + } + } + + function onResize(e) { + var el = e.srcElement; + if (el.firstChild) { + el.firstChild.style.width = el.clientWidth + 'px'; + el.firstChild.style.height = el.clientHeight + 'px'; + } + } + + G_vmlCanvasManager_.init(); + + // precompute "00" to "FF" + var dec2hex = []; + for (var i = 0; i < 16; i++) { + for (var j = 0; j < 16; j++) { + dec2hex[i * 16 + j] = i.toString(16) + j.toString(16); + } + } + + function createMatrixIdentity() { + return [ + [1, 0, 0], + [0, 1, 0], + [0, 0, 1] + ]; + } + + function matrixMultiply(m1, m2) { + var result = createMatrixIdentity(); + + for (var x = 0; x < 3; x++) { + for (var y = 0; y < 3; y++) { + var sum = 0; + + for (var z = 0; z < 3; z++) { + sum += m1[x][z] * m2[z][y]; + } + + result[x][y] = sum; + } + } + return result; + } + + function copyState(o1, o2) { + o2.fillStyle = o1.fillStyle; + o2.lineCap = o1.lineCap; + o2.lineJoin = o1.lineJoin; + o2.lineWidth = o1.lineWidth; + o2.miterLimit = o1.miterLimit; + o2.shadowBlur = o1.shadowBlur; + o2.shadowColor = o1.shadowColor; + o2.shadowOffsetX = o1.shadowOffsetX; + o2.shadowOffsetY = o1.shadowOffsetY; + o2.strokeStyle = o1.strokeStyle; + o2.globalAlpha = o1.globalAlpha; + o2.arcScaleX_ = o1.arcScaleX_; + o2.arcScaleY_ = o1.arcScaleY_; + o2.lineScale_ = o1.lineScale_; + } + + function processStyle(styleString) { + var str, alpha = 1; + + styleString = String(styleString); + if (styleString.substring(0, 3) == 'rgb') { + var start = styleString.indexOf('(', 3); + var end = styleString.indexOf(')', start + 1); + var guts = styleString.substring(start + 1, end).split(','); + + str = '#'; + for (var i = 0; i < 3; i++) { + str += dec2hex[Number(guts[i])]; + } + + if (guts.length == 4 && styleString.substr(3, 1) == 'a') { + alpha = guts[3]; + } + } else { + str = styleString; + } + + return {color: str, alpha: alpha}; + } + + function processLineCap(lineCap) { + switch (lineCap) { + case 'butt': + return 'flat'; + case 'round': + return 'round'; + case 'square': + default: + return 'square'; + } + } + + /** + * This class implements CanvasRenderingContext2D interface as described by + * the WHATWG. + * @param {HTMLElement} surfaceElement The element that the 2D context should + * be associated with + */ + function CanvasRenderingContext2D_(surfaceElement) { + this.m_ = createMatrixIdentity(); + + this.mStack_ = []; + this.aStack_ = []; + this.currentPath_ = []; + + // Canvas context properties + this.strokeStyle = '#000'; + this.fillStyle = '#000'; + + this.lineWidth = 1; + this.lineJoin = 'miter'; + this.lineCap = 'butt'; + this.miterLimit = Z * 1; + this.globalAlpha = 1; + this.canvas = surfaceElement; + + var el = surfaceElement.ownerDocument.createElement('div'); + el.style.width = surfaceElement.clientWidth + 'px'; + el.style.height = surfaceElement.clientHeight + 'px'; + el.style.overflow = 'hidden'; + el.style.position = 'absolute'; + surfaceElement.appendChild(el); + + this.element_ = el; + this.arcScaleX_ = 1; + this.arcScaleY_ = 1; + this.lineScale_ = 1; + } + + var contextPrototype = CanvasRenderingContext2D_.prototype; + contextPrototype.clearRect = function() { + this.element_.innerHTML = ''; + }; + + contextPrototype.beginPath = function() { + // TODO: Branch current matrix so that save/restore has no effect + // as per safari docs. + this.currentPath_ = []; + }; + + contextPrototype.moveTo = function(aX, aY) { + var p = this.getCoords_(aX, aY); + this.currentPath_.push({type: 'moveTo', x: p.x, y: p.y}); + this.currentX_ = p.x; + this.currentY_ = p.y; + }; + + contextPrototype.lineTo = function(aX, aY) { + var p = this.getCoords_(aX, aY); + this.currentPath_.push({type: 'lineTo', x: p.x, y: p.y}); + + this.currentX_ = p.x; + this.currentY_ = p.y; + }; + + contextPrototype.bezierCurveTo = function(aCP1x, aCP1y, + aCP2x, aCP2y, + aX, aY) { + var p = this.getCoords_(aX, aY); + var cp1 = this.getCoords_(aCP1x, aCP1y); + var cp2 = this.getCoords_(aCP2x, aCP2y); + bezierCurveTo(this, cp1, cp2, p); + }; + + // Helper function that takes the already fixed cordinates. + function bezierCurveTo(self, cp1, cp2, p) { + self.currentPath_.push({ + type: 'bezierCurveTo', + cp1x: cp1.x, + cp1y: cp1.y, + cp2x: cp2.x, + cp2y: cp2.y, + x: p.x, + y: p.y + }); + self.currentX_ = p.x; + self.currentY_ = p.y; + } + + contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) { + // the following is lifted almost directly from + // http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes + + var cp = this.getCoords_(aCPx, aCPy); + var p = this.getCoords_(aX, aY); + + var cp1 = { + x: this.currentX_ + 2.0 / 3.0 * (cp.x - this.currentX_), + y: this.currentY_ + 2.0 / 3.0 * (cp.y - this.currentY_) + }; + var cp2 = { + x: cp1.x + (p.x - this.currentX_) / 3.0, + y: cp1.y + (p.y - this.currentY_) / 3.0 + }; + + bezierCurveTo(this, cp1, cp2, p); + }; + + contextPrototype.arc = function(aX, aY, aRadius, + aStartAngle, aEndAngle, aClockwise) { + aRadius *= Z; + var arcType = aClockwise ? 'at' : 'wa'; + + var xStart = aX + mc(aStartAngle) * aRadius - Z2; + var yStart = aY + ms(aStartAngle) * aRadius - Z2; + + var xEnd = aX + mc(aEndAngle) * aRadius - Z2; + var yEnd = aY + ms(aEndAngle) * aRadius - Z2; + + // IE won't render arches drawn counter clockwise if xStart == xEnd. + if (xStart == xEnd && !aClockwise) { + xStart += 0.125; // Offset xStart by 1/80 of a pixel. Use something + // that can be represented in binary + } + + var p = this.getCoords_(aX, aY); + var pStart = this.getCoords_(xStart, yStart); + var pEnd = this.getCoords_(xEnd, yEnd); + + this.currentPath_.push({type: arcType, + x: p.x, + y: p.y, + radius: aRadius, + xStart: pStart.x, + yStart: pStart.y, + xEnd: pEnd.x, + yEnd: pEnd.y}); + + }; + + contextPrototype.rect = function(aX, aY, aWidth, aHeight) { + this.moveTo(aX, aY); + this.lineTo(aX + aWidth, aY); + this.lineTo(aX + aWidth, aY + aHeight); + this.lineTo(aX, aY + aHeight); + this.closePath(); + }; + + contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) { + var oldPath = this.currentPath_; + this.beginPath(); + + this.moveTo(aX, aY); + this.lineTo(aX + aWidth, aY); + this.lineTo(aX + aWidth, aY + aHeight); + this.lineTo(aX, aY + aHeight); + this.closePath(); + this.stroke(); + + this.currentPath_ = oldPath; + }; + + contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) { + var oldPath = this.currentPath_; + this.beginPath(); + + this.moveTo(aX, aY); + this.lineTo(aX + aWidth, aY); + this.lineTo(aX + aWidth, aY + aHeight); + this.lineTo(aX, aY + aHeight); + this.closePath(); + this.fill(); + + this.currentPath_ = oldPath; + }; + + contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1) { + var gradient = new CanvasGradient_('gradient'); + gradient.x0_ = aX0; + gradient.y0_ = aY0; + gradient.x1_ = aX1; + gradient.y1_ = aY1; + return gradient; + }; + + contextPrototype.createRadialGradient = function(aX0, aY0, aR0, + aX1, aY1, aR1) { + var gradient = new CanvasGradient_('gradientradial'); + gradient.x0_ = aX0; + gradient.y0_ = aY0; + gradient.r0_ = aR0; + gradient.x1_ = aX1; + gradient.y1_ = aY1; + gradient.r1_ = aR1; + return gradient; + }; + + contextPrototype.drawImage = function(image, var_args) { + var dx, dy, dw, dh, sx, sy, sw, sh; + + // to find the original width we overide the width and height + var oldRuntimeWidth = image.runtimeStyle.width; + var oldRuntimeHeight = image.runtimeStyle.height; + image.runtimeStyle.width = 'auto'; + image.runtimeStyle.height = 'auto'; + + // get the original size + var w = image.width; + var h = image.height; + + // and remove overides + image.runtimeStyle.width = oldRuntimeWidth; + image.runtimeStyle.height = oldRuntimeHeight; + + if (arguments.length == 3) { + dx = arguments[1]; + dy = arguments[2]; + sx = sy = 0; + sw = dw = w; + sh = dh = h; + } else if (arguments.length == 5) { + dx = arguments[1]; + dy = arguments[2]; + dw = arguments[3]; + dh = arguments[4]; + sx = sy = 0; + sw = w; + sh = h; + } else if (arguments.length == 9) { + sx = arguments[1]; + sy = arguments[2]; + sw = arguments[3]; + sh = arguments[4]; + dx = arguments[5]; + dy = arguments[6]; + dw = arguments[7]; + dh = arguments[8]; + } else { + throw Error('Invalid number of arguments'); + } + + var d = this.getCoords_(dx, dy); + + var w2 = sw / 2; + var h2 = sh / 2; + + var vmlStr = []; + + var W = 10; + var H = 10; + + // For some reason that I've now forgotten, using divs didn't work + vmlStr.push(' ' , + '', + ''); + + this.element_.insertAdjacentHTML('BeforeEnd', + vmlStr.join('')); + }; + + contextPrototype.stroke = function(aFill) { + var lineStr = []; + var lineOpen = false; + var a = processStyle(aFill ? this.fillStyle : this.strokeStyle); + var color = a.color; + var opacity = a.alpha * this.globalAlpha; + + var W = 10; + var H = 10; + + lineStr.push(''); + + if (!aFill) { + var lineWidth = this.lineScale_ * this.lineWidth; + + // VML cannot correctly render a line if the width is less than 1px. + // In that case, we dilute the color to make the line look thinner. + if (lineWidth < 1) { + opacity *= lineWidth; + } + + lineStr.push( + '' + ); + } else if (typeof this.fillStyle == 'object') { + var fillStyle = this.fillStyle; + var angle = 0; + var focus = {x: 0, y: 0}; + + // additional offset + var shift = 0; + // scale factor for offset + var expansion = 1; + + if (fillStyle.type_ == 'gradient') { + var x0 = fillStyle.x0_ / this.arcScaleX_; + var y0 = fillStyle.y0_ / this.arcScaleY_; + var x1 = fillStyle.x1_ / this.arcScaleX_; + var y1 = fillStyle.y1_ / this.arcScaleY_; + var p0 = this.getCoords_(x0, y0); + var p1 = this.getCoords_(x1, y1); + var dx = p1.x - p0.x; + var dy = p1.y - p0.y; + angle = Math.atan2(dx, dy) * 180 / Math.PI; + + // The angle should be a non-negative number. + if (angle < 0) { + angle += 360; + } + + // Very small angles produce an unexpected result because they are + // converted to a scientific notation string. + if (angle < 1e-6) { + angle = 0; + } + } else { + var p0 = this.getCoords_(fillStyle.x0_, fillStyle.y0_); + var width = max.x - min.x; + var height = max.y - min.y; + focus = { + x: (p0.x - min.x) / width, + y: (p0.y - min.y) / height + }; + + width /= this.arcScaleX_ * Z; + height /= this.arcScaleY_ * Z; + var dimension = m.max(width, height); + shift = 2 * fillStyle.r0_ / dimension; + expansion = 2 * fillStyle.r1_ / dimension - shift; + } + + // We need to sort the color stops in ascending order by offset, + // otherwise IE won't interpret it correctly. + var stops = fillStyle.colors_; + stops.sort(function(cs1, cs2) { + return cs1.offset - cs2.offset; + }); + + var length = stops.length; + var color1 = stops[0].color; + var color2 = stops[length - 1].color; + var opacity1 = stops[0].alpha * this.globalAlpha; + var opacity2 = stops[length - 1].alpha * this.globalAlpha; + + var colors = []; + for (var i = 0; i < length; i++) { + var stop = stops[i]; + colors.push(stop.offset * expansion + shift + ' ' + stop.color); + } + + // When colors attribute is used, the meanings of opacity and o:opacity2 + // are reversed. + lineStr.push(''); + } else { + lineStr.push(''); + } + + lineStr.push(''); + + this.element_.insertAdjacentHTML('beforeEnd', lineStr.join('')); + }; + + contextPrototype.fill = function() { + this.stroke(true); + } + + contextPrototype.closePath = function() { + this.currentPath_.push({type: 'close'}); + }; + + /** + * @private + */ + contextPrototype.getCoords_ = function(aX, aY) { + var m = this.m_; + return { + x: Z * (aX * m[0][0] + aY * m[1][0] + m[2][0]) - Z2, + y: Z * (aX * m[0][1] + aY * m[1][1] + m[2][1]) - Z2 + } + }; + + contextPrototype.save = function() { + var o = {}; + copyState(this, o); + this.aStack_.push(o); + this.mStack_.push(this.m_); + this.m_ = matrixMultiply(createMatrixIdentity(), this.m_); + }; + + contextPrototype.restore = function() { + copyState(this.aStack_.pop(), this); + this.m_ = this.mStack_.pop(); + }; + + function matrixIsFinite(m) { + for (var j = 0; j < 3; j++) { + for (var k = 0; k < 2; k++) { + if (!isFinite(m[j][k]) || isNaN(m[j][k])) { + return false; + } + } + } + return true; + } + + function setM(ctx, m, updateLineScale) { + if (!matrixIsFinite(m)) { + return; + } + ctx.m_ = m; + + if (updateLineScale) { + // Get the line scale. + // Determinant of this.m_ means how much the area is enlarged by the + // transformation. So its square root can be used as a scale factor + // for width. + var det = m[0][0] * m[1][1] - m[0][1] * m[1][0]; + ctx.lineScale_ = sqrt(abs(det)); + } + } + + contextPrototype.translate = function(aX, aY) { + var m1 = [ + [1, 0, 0], + [0, 1, 0], + [aX, aY, 1] + ]; + + setM(this, matrixMultiply(m1, this.m_), false); + }; + + contextPrototype.rotate = function(aRot) { + var c = mc(aRot); + var s = ms(aRot); + + var m1 = [ + [c, s, 0], + [-s, c, 0], + [0, 0, 1] + ]; + + setM(this, matrixMultiply(m1, this.m_), false); + }; + + contextPrototype.scale = function(aX, aY) { + this.arcScaleX_ *= aX; + this.arcScaleY_ *= aY; + var m1 = [ + [aX, 0, 0], + [0, aY, 0], + [0, 0, 1] + ]; + + setM(this, matrixMultiply(m1, this.m_), true); + }; + + contextPrototype.transform = function(m11, m12, m21, m22, dx, dy) { + var m1 = [ + [m11, m12, 0], + [m21, m22, 0], + [dx, dy, 1] + ]; + + setM(this, matrixMultiply(m1, this.m_), true); + }; + + contextPrototype.setTransform = function(m11, m12, m21, m22, dx, dy) { + var m = [ + [m11, m12, 0], + [m21, m22, 0], + [dx, dy, 1] + ]; + + setM(this, m, true); + }; + + /******** STUBS ********/ + contextPrototype.clip = function() { + // TODO: Implement + }; + + contextPrototype.arcTo = function() { + // TODO: Implement + }; + + contextPrototype.createPattern = function() { + return new CanvasPattern_; + }; + + // Gradient / Pattern Stubs + function CanvasGradient_(aType) { + this.type_ = aType; + this.x0_ = 0; + this.y0_ = 0; + this.r0_ = 0; + this.x1_ = 0; + this.y1_ = 0; + this.r1_ = 0; + this.colors_ = []; + } + + CanvasGradient_.prototype.addColorStop = function(aOffset, aColor) { + aColor = processStyle(aColor); + this.colors_.push({offset: aOffset, + color: aColor.color, + alpha: aColor.alpha}); + }; + + function CanvasPattern_() {} + + // set up externs + G_vmlCanvasManager = G_vmlCanvasManager_; + CanvasRenderingContext2D = CanvasRenderingContext2D_; + CanvasGradient = CanvasGradient_; + CanvasPattern = CanvasPattern_; + +})(); + +} // if diff --git a/index.html b/index.html old mode 100644 new mode 100755 index ed88799..f5ecfc7 --- a/index.html +++ b/index.html @@ -2,167 +2,267 @@ jQuery Knob demo - - + + + -
- Knob demo - Flattr this +
+

jQuery Knob

+
+
+

Nice, downward compatible, touchable, jQuery dial. Flattr this

+

* implemented interactions : mouse click and wheel mouse, keyboard (on focus) and fingers (touch events)

-
+
+

× Disable display input

-data-width="75"
-data-fgColor="#fff"
-data-skin="tron"
-data-displayPrevious=true
+data-width="100"
+data-displayInput=false
             
- +
-
+
+

× 'cursor' mode

 data-width="150"
-data-fgColor="#fff"
-data-skin="tron"
-data-thickness=".2"
-data-displayPrevious=true
+data-cursor=true
+data-thickness=.3
+data-fgColor="#222222"
             
- +
-
+
+

× Display previous value

-data-width="150"
-data-fgColor="#fff"
-data-skin="tron"
-data-thickness=".1"
+data-displayPrevious=true
+data-min="-100"
             
- +
-
+
+

× Angle offset

-data-width="100"
-data-displayInput=false
+data-angleOffset=90
+data-linecap=round
             
- +
-
+
+

× Angle offset and arc

-data-width="200"
-data-cursor=true
+data-fgColor="#66CC66"
+data-angleOffset=-125
+data-angleArc=250
+data-rotation=anticlockwise
             
- +
-
+
+

× 4-digit, step 0.1

-data-width="200"
-data-min="-100"
-data-cgColor="#A9EFFD"
+data-step=".1"
+data-min="-10000"
+data-max="10000"
+value="0"
 data-displayPrevious=true
-data-angleOffset="90"
             
- +
-
-
-data-width="200"
-data-min="0"
-data-max="100"
-data-cgColor="#A9EFFD"
-data-displayPrevious=true
-data-angleOffset="-135"
-data-angleArc="270"
-            
- +
+
+

× Overloaded 'draw' method

+
+
+
+
+    data-width="75"
+    data-fgColor="#ffec03"
+    data-skin="tron"
+    data-thickness=".2"
+    data-displayPrevious=true
+                
+ +
+
+
+    data-width="150"
+    data-fgColor="#ffec03"
+    data-skin="tron"
+    data-thickness=".2"
+    data-displayPrevious=true
+                
+ +
+
+
+    data-width="150"
+    data-fgColor="#C0ffff"
+    data-skin="tron"
+    data-thickness=".1"
+    data-angleOffset="180"
+                
+ +
-
-

Ticks

+
+

× Responsive

-data-cursor=true
-data-skin="tron"
-data-ticks="8"
+data-width="80%"
             
- +
+ + Current div width is 30% of window width.
+ Knob width is 80% of current div.
+ Knob width is 80% of 30% of window width.
+ Test resizing window. +
+
+
+ +
+
+
+ +
+

× Superpose (clock)

+
+
+ +
+
+ +
+
+ +
+
-
-

Readonly

+
+
+

× Readonly

+readonly (or data-readOnly=true)
 data-thickness=".4"
 data-fgColor="chartreuse"
-data-readOnly=true
             
- +
-
-

Knobify!

+
+

× Dynamic

 data-width="200"
             
@@ -175,10 +275,9 @@

Knobify!

-
-
-

Infinite / iPod click wheel

-
+
+

× Infinite || iPod click wheel

+
 data-width="150"
 data-cursor=true
@@ -188,27 +287,17 @@ 

Infinite / iPod click wheel

data-displayInput="false" + some code
- +
-
0
-
+
0
+
-
- - +
+
+ +

jQuery Knob is © 2012 Anthony Terrien - MIT License

+
- \ No newline at end of file + diff --git a/js/jquery.knob.js b/js/jquery.knob.js index a221cd1..5294ffa 100644 --- a/js/jquery.knob.js +++ b/js/jquery.knob.js @@ -1,434 +1,805 @@ +/*!jQuery Knob*/ /** - * Knob - jQuery Plugin * Downward compatible, touchable dial * - * Version: 1.1.2 (22/05/2012) + * Version: 1.2.11 * Requires: jQuery v1.7+ * - * Copyright (c) 2011 Anthony Terrien - * Under MIT and GPL licenses: - * http://www.opensource.org/licenses/mit-license.php - * http://www.gnu.org/licenses/gpl.html + * Copyright (c) 2012 Anthony Terrien + * Under MIT License (http://www.opensource.org/licenses/mit-license.php) * - * Thanks to vor, eskimoblood, spiffistan + * Thanks to vor, eskimoblood, spiffistan, FabrizioC */ -$(function () { - - // Dial logic - var Dial = function (c, opt) { - - var v = null - ,ctx = c[0].getContext("2d") - ,PI2 = 2 * Math.PI - ,mx ,my ,x ,y - ,self = this; - - this.onChange = function () {}; - this.onCancel = function () {}; - this.onRelease = function () {}; - - this.val = function (nv) { - if (null != nv) { - opt.stopper && (nv = Math.max(Math.min(nv, opt.max), opt.min)); - v = nv; - this.onChange(nv); - this.draw(nv); - } else { - var b, a; - b = a = Math.atan2(mx - x, -(my - y - opt.width / 2)) - opt.angleOffset; -// (a < 0) && (b = a + PI2); - (a < 0) && (b = a + opt.angleArc); -// nv = Math.round(b * (opt.max - opt.min) / PI2) + opt.min; - nv = Math.round(b * (opt.max - opt.min) / opt.angleArc) + opt.min; - return (nv > opt.max) ? opt.max : nv; +(function (factory) { + if (typeof exports === 'object') { + // CommonJS + module.exports = factory(require('jquery')); + } else if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(['jquery'], factory); + } else { + // Browser globals + factory(jQuery); + } +}(function ($) { + + /** + * Kontrol library + */ + "use strict"; + + /** + * Definition of globals and core + */ + var k = {}, // kontrol + max = Math.max, + min = Math.min; + + k.c = {}; + k.c.d = $(document); + k.c.t = function (e) { + return e.originalEvent.touches.length - 1; + }; + + /** + * Kontrol Object + * + * Definition of an abstract UI control + * + * Each concrete component must call this one. + * + * k.o.call(this); + * + */ + k.o = function () { + var s = this; + + this.o = null; // array of options + this.$ = null; // jQuery wrapped element + this.i = null; // mixed HTMLInputElement or array of HTMLInputElement + this.g = null; // deprecated 2D graphics context for 'pre-rendering' + this.v = null; // value ; mixed array or integer + this.cv = null; // change value ; not commited value + this.x = 0; // canvas x position + this.y = 0; // canvas y position + this.w = 0; // canvas width + this.h = 0; // canvas height + this.$c = null; // jQuery canvas element + this.c = null; // rendered canvas context + this.t = 0; // touches index + this.isInit = false; + this.fgColor = null; // main color + this.pColor = null; // previous color + this.dH = null; // draw hook + this.cH = null; // change hook + this.eH = null; // cancel hook + this.rH = null; // release hook + this.scale = 1; // scale factor + this.relative = false; + this.relativeWidth = false; + this.relativeHeight = false; + this.$div = null; // component div + + this.run = function () { + var cf = function (e, conf) { + var k; + for (k in conf) { + s.o[k] = conf[k]; + } + s._carve().init(); + s._configure() + ._draw(); + }; + + if (this.$.data('kontroled')) return; + this.$.data('kontroled', true); + + this.extend(); + this.o = $.extend({ + // Config + min: this.$.data('min') !== undefined ? this.$.data('min') : 0, + max: this.$.data('max') !== undefined ? this.$.data('max') : 100, + stopper: true, + readOnly: this.$.data('readonly') || (this.$.attr('readonly') === 'readonly'), + + // UI + cursor: this.$.data('cursor') === true && 30 + || this.$.data('cursor') || 0, + thickness: this.$.data('thickness') + && Math.max(Math.min(this.$.data('thickness'), 1), 0.01) + || 0.35, + lineCap: this.$.data('linecap') || 'butt', + width: this.$.data('width') || 200, + height: this.$.data('height') || 200, + displayInput: this.$.data('displayinput') == null || this.$.data('displayinput'), + displayPrevious: this.$.data('displayprevious'), + fgColor: this.$.data('fgcolor') || '#87CEEB', + inputColor: this.$.data('inputcolor'), + font: this.$.data('font') || 'Arial', + fontWeight: this.$.data('font-weight') || 'bold', + inline: false, + step: this.$.data('step') || 1, + rotation: this.$.data('rotation'), + + // Hooks + draw: null, // function () {} + change: null, // function (value) {} + cancel: null, // function () {} + release: null, // function (value) {} + + // Output formatting, allows to add unit: %, ms ... + format: function(v) { + return v; + }, + parse: function (v) { + return parseFloat(v); + } + }, this.o + ); + + // finalize options + this.o.flip = this.o.rotation === 'anticlockwise' || this.o.rotation === 'acw'; + if (!this.o.inputColor) { + this.o.inputColor = this.o.fgColor; } - }; - this.change = function (nv) { - opt.stopper && (nv = Math.max(Math.min(nv, opt.max), opt.min)); - this.onChange(nv); - this.draw(nv); - }; + // routing value + if (this.$.is('fieldset')) { + + // fieldset = array of integer + this.v = {}; + this.i = this.$.find('input'); + this.i.each(function(k) { + var $this = $(this); + s.i[k] = $this; + s.v[k] = s.o.parse($this.val()); + + $this.bind( + 'change blur', + function () { + var val = {}; + val[k] = $this.val(); + s.val(s._validate(val)); + } + ); + }); + this.$.find('legend').remove(); + } else { - this.angle = function (nv) { - return (nv - opt.min) * opt.angleArc / (opt.max - opt.min); - }; + // input = integer + this.i = this.$; + this.v = this.o.parse(this.$.val()); + this.v === '' && (this.v = this.o.min); + this.$.bind( + 'change blur', + function () { + s.val(s._validate(s.o.parse(s.$.val()))); + } + ); + + } - this.draw = function (nv) { + !this.o.displayInput && this.$.hide(); - var a = this.angle(nv) // Angle - ,sa = 1.5 * Math.PI + opt.angleOffset // Previous start angle - ,sat = sa // Start angle - ,ea = sa + this.angle(v) // Previous end angle - ,eat = sat + a // End angle - ,r = opt.width / 2 // Radius - ,lw = r * opt.thickness // Line width - ,cgcolor = Dial.getCgColor(opt.cgColor) - ,tick - ,bsa = sa - ,bea = bsa + (opt.angleBgCircle ? (2 * Math.PI) : opt.angleArc) - ; + // adds needed DOM elements (canvas, div) + this.$c = $(document.createElement('canvas')).attr({ + width: this.o.width, + height: this.o.height + }); - ctx.clearRect(0, 0, opt.width, opt.width); - ctx.lineWidth = lw; + // wraps all elements in a div + // add to DOM before Canvas init is triggered + this.$div = $('
'); - // Hook draw - if (opt.draw(a, v, opt, ctx)) { return; } + this.$.wrap(this.$div).before(this.$c); + this.$div = this.$.parent(); - for (tick = 0; tick < opt.ticks; tick++) { + if (typeof G_vmlCanvasManager !== 'undefined') { + G_vmlCanvasManager.initElement(this.$c[0]); + } - ctx.beginPath(); + this.c = this.$c[0].getContext ? this.$c[0].getContext('2d') : null; - if (a > (((2 * Math.PI) / opt.ticks) * tick) && opt.tickColorizeValues) { - ctx.strokeStyle = opt.fgColor; - } else { - ctx.strokeStyle = opt.tickColor; + if (!this.c) { + throw { + name: "CanvasNotSupportedException", + message: "Canvas not supported. Please use excanvas on IE8.0.", + toString: function(){return this.name + ": " + this.message} } + } - var tick_sa = (((2 * Math.PI) / opt.ticks) * tick) - (0.5 * Math.PI); - ctx.arc( r, r, r-lw-opt.tickLength, tick_sa, tick_sa+opt.tickWidth , false); - ctx.stroke(); + // hdpi support + this.scale = (window.devicePixelRatio || 1) / ( + this.c.webkitBackingStorePixelRatio || + this.c.mozBackingStorePixelRatio || + this.c.msBackingStorePixelRatio || + this.c.oBackingStorePixelRatio || + this.c.backingStorePixelRatio || 1 + ); + + // detects relative width / height + this.relativeWidth = this.o.width % 1 !== 0 + && this.o.width.indexOf('%'); + this.relativeHeight = this.o.height % 1 !== 0 + && this.o.height.indexOf('%'); + this.relative = this.relativeWidth || this.relativeHeight; + + // computes size and carves the component + this._carve(); + + // prepares props for transaction + if (this.v instanceof Object) { + this.cv = {}; + this.copy(this.v, this.cv); + } else { + this.cv = this.v; } - opt.cursor - && (sa = ea - 0.3) - && (ea = ea + 0.3) - && (sat = eat - 0.3) - && (eat = eat + 0.3); + // binds configure event + this.$ + .bind("configure", cf) + .parent() + .bind("configure", cf); + + // finalize init + this._listen() + ._configure() + ._xy() + .init(); + + this.isInit = true; + + this.$.val(this.o.format(this.v)); + this._draw(); + + return this; + }; + + this._carve = function() { + if (this.relative) { + var w = this.relativeWidth ? + this.$div.parent().width() * + parseInt(this.o.width) / 100 + : this.$div.parent().width(), + h = this.relativeHeight ? + this.$div.parent().height() * + parseInt(this.o.height) / 100 + : this.$div.parent().height(); + + // apply relative + this.w = this.h = Math.min(w, h); + } else { + this.w = this.o.width; + this.h = this.o.height; + } + + // finalize div + this.$div.css({ + 'width': this.w + 'px', + 'height': this.h + 'px' + }); + + // finalize canvas with computed width + this.$c.attr({ + width: this.w, + height: this.h + }); + + // scaling + if (this.scale !== 1) { + this.$c[0].width = this.$c[0].width * this.scale; + this.$c[0].height = this.$c[0].height * this.scale; + this.$c.width(this.w); + this.$c.height(this.h); + } + + return this; + } + + this._draw = function () { + + // canvas pre-rendering + var d = true; + + s.g = s.c; - switch (opt.skin) { + s.clear(); - case 'default' : + s.dH && (d = s.dH()); - ctx.beginPath(); - ctx.strokeStyle = opt.bgColor; - ctx.arc(r, r, r - lw / 2, bea, bsa, true); - ctx.stroke(); + d !== false && s.draw(); + }; + + this._touch = function (e) { + var touchMove = function (e) { + var v = s.xy2val( + e.originalEvent.touches[s.t].pageX, + e.originalEvent.touches[s.t].pageY + ); + + if (v == s.cv) return; + + if (s.cH && s.cH(v) === false) return; - if (opt.displayPrevious) { - ctx.beginPath(); - ctx.strokeStyle = (v == nv) ? opt.fgColor : cgcolor; - ctx.arc(r, r, r - lw / 2, sa, ea, false); - ctx.stroke(); + s.change(s._validate(v)); + s._draw(); + }; + + // get touches index + this.t = k.c.t(e); + + // First touch + touchMove(e); + + // Touch events listeners + k.c.d + .bind("touchmove.k", touchMove) + .bind( + "touchend.k", + function () { + k.c.d.unbind('touchmove.k touchend.k'); + s.val(s.cv); } + ); + + return this; + }; + + this._mouse = function (e) { + var mouseMove = function (e) { + var v = s.xy2val(e.pageX, e.pageY); - ctx.beginPath(); - ctx.strokeStyle = opt.fgColor; - ctx.arc(r, r, r - lw / 2, sat, eat, false); - ctx.stroke(); + if (v == s.cv) return; - break; + if (s.cH && (s.cH(v) === false)) return; + + s.change(s._validate(v)); + s._draw(); + }; + + // First click + mouseMove(e); + + // Mouse events listeners + k.c.d + .bind("mousemove.k", mouseMove) + .bind( + // Escape key cancel current change + "keyup.k", + function (e) { + if (e.keyCode === 27) { + k.c.d.unbind("mouseup.k mousemove.k keyup.k"); - case 'tron' : + if (s.eH && s.eH() === false) + return; - if (opt.displayPrevious) { - ctx.beginPath(); - ctx.strokeStyle = (v == nv) ? opt.fgColor : cgcolor; - ctx.arc( r, r, r - lw, sa, ea, false); - ctx.stroke(); + s.cancel(); + } } + ) + .bind( + "mouseup.k", + function (e) { + k.c.d.unbind('mousemove.k mouseup.k keyup.k'); + s.val(s.cv); + } + ); - ctx.beginPath(); - ctx.strokeStyle = opt.fgColor; - ctx.arc( r, r, r - lw, sat, eat, false); - ctx.stroke(); + return this; + }; + + this._xy = function () { + var o = this.$c.offset(); + this.x = o.left; + this.y = o.top; - ctx.lineWidth = 2; - ctx.beginPath(); - ctx.strokeStyle = opt.fgColor; - ctx.arc( r, r, r - lw + 1 + lw * 2 / 3, 0, 2 * Math.PI, false); - ctx.stroke(); + return this; + }; - break; + this._listen = function () { + if (!this.o.readOnly) { + this.$c + .bind( + "mousedown", + function (e) { + e.preventDefault(); + s._xy()._mouse(e); + } + ) + .bind( + "touchstart", + function (e) { + e.preventDefault(); + s._xy()._touch(e); + } + ); + + this.listen(); + } else { + this.$.attr('readonly', 'readonly'); } + + if (this.relative) { + $(window).resize(function() { + s._carve().init(); + s._draw(); + }); + } + + return this; }; - this.capture = function (e) { - switch (e.type) { - case 'mousemove' : - case 'mousedown' : - mx = e.pageX; - my = e.pageY; - break; - case 'touchmove' : - case 'touchstart' : - mx = e.originalEvent.touches[0].pageX; - my = e.originalEvent.touches[0].pageY; - break; + this._configure = function () { + + // Hooks + if (this.o.draw) this.dH = this.o.draw; + if (this.o.change) this.cH = this.o.change; + if (this.o.cancel) this.eH = this.o.cancel; + if (this.o.release) this.rH = this.o.release; + + if (this.o.displayPrevious) { + this.pColor = this.h2rgba(this.o.fgColor, "0.4"); + this.fgColor = this.h2rgba(this.o.fgColor, "0.6"); + } else { + this.fgColor = this.o.fgColor; } - this.change( this.val() ); + + return this; }; - this.cancel = function () { - self.val(v); - self.onCancel(); + this._clear = function () { + this.$c[0].width = this.$c[0].width; + }; + + this._validate = function (v) { + var val = (~~ (((v < 0) ? -0.5 : 0.5) + (v/this.o.step))) * this.o.step; + return Math.round(val * 100) / 100; + }; + + // Abstract methods + this.listen = function () {}; // on start, one time + this.extend = function () {}; // each time configure triggered + this.init = function () {}; // each time configure triggered + this.change = function (v) {}; // on change + this.val = function (v) {}; // on release + this.xy2val = function (x, y) {}; // + this.draw = function () {}; // on change / on release + this.clear = function () { this._clear(); }; + + // Utils + this.h2rgba = function (h, a) { + var rgb; + h = h.substring(1,7) + rgb = [ + parseInt(h.substring(0,2), 16), + parseInt(h.substring(2,4), 16), + parseInt(h.substring(4,6), 16) + ]; + + return "rgba(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + "," + a + ")"; + }; + + this.copy = function (f, t) { + for (var i in f) { + t[i] = f[i]; + } + }; + }; + + + /** + * k.Dial + */ + k.Dial = function () { + k.o.call(this); + + this.startAngle = null; + this.xy = null; + this.radius = null; + this.lineWidth = null; + this.cursorExt = null; + this.w2 = null; + this.PI2 = 2*Math.PI; + + this.extend = function () { + this.o = $.extend({ + bgColor: this.$.data('bgcolor') || '#EEEEEE', + angleOffset: this.$.data('angleoffset') || 0, + angleArc: this.$.data('anglearc') || 360, + inline: true + }, this.o); + }; + + this.val = function (v, triggerRelease) { + if (null != v) { + + // reverse format + v = this.o.parse(v); + + if (triggerRelease !== false + && v != this.v + && this.rH + && this.rH(v) === false) { return; } + + this.cv = this.o.stopper ? max(min(v, this.o.max), this.o.min) : v; + this.v = this.cv; + this.$.val(this.o.format(this.v)); + this._draw(); + } else { + return this.v; + } }; - this.startDrag = function (e) { + this.xy2val = function (x, y) { + var a, ret; - var p = c.offset() - ,$doc = $(document); + a = Math.atan2( + x - (this.x + this.w2), + - (y - this.y - this.w2) + ) - this.angleOffset; - x = p.left + (opt.width / 2); - y = p.top; + if (this.o.flip) { + a = this.angleArc - a - this.PI2; + } + + if (this.angleArc != this.PI2 && (a < 0) && (a > -0.5)) { + + // if isset angleArc option, set to min if .5 under min + a = 0; + } else if (a < 0) { + a += this.PI2; + } - this.capture(e); + ret = (a * (this.o.max - this.o.min) / this.angleArc) + this.o.min; - // Listen mouse and touch events - $doc.bind( - "mousemove.dial touchmove.dial" - ,function (e) { - self.capture(e); + this.o.stopper && (ret = max(min(ret, this.o.max), this.o.min)); + + return ret; + }; + + this.listen = function () { + + // bind MouseWheel + var s = this, mwTimerStop, + mwTimerRelease, + mw = function (e) { + e.preventDefault(); + + var ori = e.originalEvent, + deltaX = ori.detail || ori.wheelDeltaX, + deltaY = ori.detail || ori.wheelDeltaY, + v = s._validate(s.o.parse(s.$.val())) + + ( + deltaX > 0 || deltaY > 0 + ? s.o.step + : deltaX < 0 || deltaY < 0 ? -s.o.step : 0 + ); + + v = max(min(v, s.o.max), s.o.min); + + s.val(v, false); + + if (s.rH) { + // Handle mousewheel stop + clearTimeout(mwTimerStop); + mwTimerStop = setTimeout(function () { + s.rH(v); + mwTimerStop = null; + }, 100); + + // Handle mousewheel releases + if (!mwTimerRelease) { + mwTimerRelease = setTimeout(function () { + if (mwTimerStop) + s.rH(v); + mwTimerRelease = null; + }, 200); + } } - ) + }, + kval, + to, + m = 1, + kv = { + 37: -s.o.step, + 38: s.o.step, + 39: s.o.step, + 40: -s.o.step + }; + + this.$ .bind( - // Escape - "keyup.dial" - ,function (e) { - if(e.keyCode === 27) { - $doc.unbind("mouseup.dial mousemove.dial keyup.dial"); - self.cancel(); + "keydown", + function (e) { + var kc = e.keyCode; + + // numpad support + if (kc >= 96 && kc <= 105) { + kc = e.keyCode = kc - 48; + } + + kval = parseInt(String.fromCharCode(kc)); + + if (isNaN(kval)) { + (kc !== 13) // enter + && kc !== 8 // bs + && kc !== 9 // tab + && kc !== 189 // - + && (kc !== 190 + || s.$.val().match(/\./)) // . allowed once + && e.preventDefault(); + + // arrows + if ($.inArray(kc,[37,38,39,40]) > -1) { + e.preventDefault(); + + var v = s.o.parse(s.$.val()) + kv[kc] * m; + s.o.stopper && (v = max(min(v, s.o.max), s.o.min)); + + s.change(s._validate(v)); + s._draw(); + + // long time keydown speed-up + to = window.setTimeout(function () { + m *= 2; + }, 30); + } } } ) .bind( - "mouseup.dial touchend.dial" - ,function (e) { - $doc.unbind('mousemove.dial touchmove.dial mouseup.dial touchend.dial keyup.dial'); - self.val(self.val()); - self.onRelease(v); + "keyup", + function (e) { + if (isNaN(kval)) { + if (to) { + window.clearTimeout(to); + to = null; + m = 1; + s.val(s.$.val()); + } + } else { + // kval postcond + (s.$.val() > s.o.max && s.$.val(s.o.max)) + || (s.$.val() < s.o.min && s.$.val(s.o.min)); + } } ); + + this.$c.bind("mousewheel DOMMouseScroll", mw); + this.$.bind("mousewheel DOMMouseScroll", mw) }; - }; - // Dial static func - Dial.getCgColor = function (h) { - h = h.substring(1,7); - var rgb = [parseInt(h.substring(0,2),16) - ,parseInt(h.substring(2,4),16) - ,parseInt(h.substring(4,6),16)]; - return "rgba("+rgb[0]+","+rgb[1]+","+rgb[2]+",.5)"; - }; + this.init = function () { + if (this.v < this.o.min + || this.v > this.o.max) { this.v = this.o.min; } + + this.$.val(this.v); + this.w2 = this.w / 2; + this.cursorExt = this.o.cursor / 100; + this.xy = this.w2 * this.scale; + this.lineWidth = this.xy * this.o.thickness; + this.lineCap = this.o.lineCap; + this.radius = this.xy - this.lineWidth / 2; + + this.o.angleOffset + && (this.o.angleOffset = isNaN(this.o.angleOffset) ? 0 : this.o.angleOffset); + + this.o.angleArc + && (this.o.angleArc = isNaN(this.o.angleArc) ? this.PI2 : this.o.angleArc); + + // deg to rad + this.angleOffset = this.o.angleOffset * Math.PI / 180; + this.angleArc = this.o.angleArc * Math.PI / 180; + + // compute start and end angles + this.startAngle = 1.5 * Math.PI + this.angleOffset; + this.endAngle = 1.5 * Math.PI + this.angleOffset + this.angleArc; + + var s = max( + String(Math.abs(this.o.max)).length, + String(Math.abs(this.o.min)).length, + 2 + ) + 2; + + this.o.displayInput + && this.i.css({ + 'width' : ((this.w / 2 + 4) >> 0) + 'px', + 'height' : ((this.w / 3) >> 0) + 'px', + 'position' : 'absolute', + 'vertical-align' : 'middle', + 'margin-top' : ((this.w / 3) >> 0) + 'px', + 'margin-left' : '-' + ((this.w * 3 / 4 + 2) >> 0) + 'px', + 'border' : 0, + 'background' : 'none', + 'font' : this.o.fontWeight + ' ' + ((this.w / s) >> 0) + 'px ' + this.o.font, + 'text-align' : 'center', + 'color' : this.o.inputColor || this.o.fgColor, + 'padding' : '0px', + '-webkit-appearance': 'none' + }) || this.i.css({ + 'width': '0px', + 'visibility': 'hidden' + }); + }; - // jQuery plugin - $.fn.knob = $.fn.dial = function (gopt) { + this.change = function (v) { + this.cv = v; + this.$.val(this.o.format(v)); + }; - return this.each( + this.angle = function (v) { + return (v - this.o.min) * this.angleArc / (this.o.max - this.o.min); + }; - function () { + this.arc = function (v) { + var sa, ea; + v = this.angle(v); + if (this.o.flip) { + sa = this.endAngle + 0.00001; + ea = sa - v - 0.00001; + } else { + sa = this.startAngle - 0.00001; + ea = sa + v + 0.00001; + } + this.o.cursor + && (sa = ea - this.cursorExt) + && (ea = ea + this.cursorExt); + + return { + s: sa, + e: ea, + d: this.o.flip && !this.o.cursor + }; + }; - var $this = $(this), opt; - - if ($this.data('dialed')) { return $this; } - $this.data('dialed', true); - - opt = $.extend( - { - // Config - 'min' : $this.data('min') || 0 - ,'max' : $this.data('max') || 100 - ,'stopper' : true - ,'readOnly' : $this.data('readonly') - - // UI - ,'cursor' : $this.data('cursor') - ,'thickness' : $this.data('thickness') || 0.35 - ,'width' : $this.data('width') || 200 - ,'displayInput' : $this.data('displayinput') == null || $this.data('displayinput') - ,'displayPrevious' : $this.data('displayprevious') - ,'fgColor' : $this.data('fgcolor') || '#87CEEB' - ,'cgColor' : $this.data('cgcolor') || $this.data('fgcolor') || '#87CEEB' - ,'bgColor' : $this.data('bgcolor') || '#EEEEEE' - ,'tickColor' : $this.data('tickColor') || $this.data('fgcolor') || '#DDDDDD' - ,'ticks' : $this.data('ticks') || 0 - ,'tickLength' : $this.data('tickLength') || 0 - ,'tickWidth' : $this.data('tickWidth') || 0.02 - ,'tickColorizeValues' : $this.data('tickColorizeValues') || true - ,'skin' : $this.data('skin') || 'default' - ,'angleOffset': degreeToRadians($this.data('angleoffset')) - ,'angleArc': degreeToRadians($this.data('anglearc')) || 2 * Math.PI - ,'angleBgCircle': $this.data('anglebgcircle') || false - - - // Hooks - ,'draw' : - /** - * @param int a angle - * @param int v current value - * @param array opt plugin options - * @param context ctx Canvas context 2d - * @return bool true:bypass default draw methode - */ - function (a, v, opt, ctx) {} - ,'change' : - /** - * @param int v Current value - */ - function (v) {} - ,'release' : - /** - * @param int v Current value - * @param jQuery ipt Input - */ - function (v, ipt) {} - } - ,gopt - ); + this.draw = function () { + var c = this.g, // context + a = this.arc(this.cv), // Arc + pa, // Previous arc + r = 1; - var c = $('') - ,wd = $('
') - ,k - ,vl = $this.val() - ,initStyle = function () { - opt.displayInput - && $this.css({ - 'width' : opt.width / 2 + 'px' - ,'position' : 'absolute' - ,'margin-top' : (opt.width * 5 / 14) + 'px' - ,'margin-left' : '-' + (opt.width * 3 / 4) + 'px' - ,'font-size' : (opt.width / 4) + 'px' - ,'border' : 'none' - ,'background' : 'none' - ,'font-family' : 'Arial' - ,'font-weight' : 'bold' - ,'text-align' : 'center' - ,'color' : opt.fgColor - ,'padding' : '0px' - ,'-webkit-appearance': 'none' - }) - || $this.css({ - 'width' : '0px' - ,'visibility' : 'hidden' - }); - }; - - // Canvas insert - $this.wrap(wd).before(c); - - initStyle(); - - // Invoke dial logic - k = new Dial(c, opt); - vl || (vl = opt.min); - $this.val(vl); - k.val(vl); - - k.onRelease = function (v) { - opt.release(v, $this); - }; - k.onChange = function (v) { - $this.val(v); - opt.change(v); - }; - - // bind change on input - $this.bind( - 'change' - ,function (e) { - k.val($this.val()); - } - ); + c.lineWidth = this.lineWidth; + c.lineCap = this.lineCap; - if (!opt.readOnly) { - - // canvas - c.bind( - "mousedown touchstart" - ,function (e) { - e.preventDefault(); - k.startDrag(e); - } - ) - .bind( - "mousewheel DOMMouseScroll" - ,mw = function (e) { - e.preventDefault(); - var ori = e.originalEvent - ,deltaX = ori.detail || ori.wheelDeltaX - ,deltaY = ori.detail || ori.wheelDeltaY - ,val = parseInt($this.val()) + (deltaX>0 || deltaY>0 ? 1 : deltaX<0 || deltaY<0 ? -1 : 0); - k.val(val); - } - ); + if (this.o.bgColor !== "none") { + c.beginPath(); + c.strokeStyle = this.o.bgColor; + c.arc(this.xy, this.xy, this.radius, this.endAngle - 0.00001, this.startAngle + 0.00001, true); + c.stroke(); + } - // input - var kval, val, to, m = 1, kv = {37:-1, 38:1, 39:1, 40:-1}; - $this - .bind( - "configure" - ,function (e, aconf) { - var kconf; - for (kconf in aconf) { opt[kconf] = aconf[kconf]; } - initStyle(); - k.val($this.val()); - } - ) - .bind( - "keydown" - ,function (e) { - var kc = e.keyCode; - if (kc >= 96 && kc <= 105) kc -= 48; //numpad - kval = parseInt(String.fromCharCode(kc)); - - if (isNaN(kval)) { - - (kc !== 13) // enter - && (kc !== 8) // bs - && (kc !== 9) // tab - && (kc !== 189) // - - && e.preventDefault(); - - // arrows - if ($.inArray(kc,[37,38,39,40]) > -1) { - k.change(parseInt($this.val()) + kv[kc] * m); - - // long time keydown speed-up - to = window.setTimeout( - function () { m < 20 && m++; } - ,50 - ); - - e.preventDefault(); - } - } - } - ) - .bind( - "keyup" - ,function(e) { - if (isNaN(kval)) { - if (to) { - window.clearTimeout(to); - to = null; - m = 1; - k.val($this.val()); - k.onRelease($this.val(), $this); - } else { - // enter - (e.keyCode === 13) - && k.onRelease($this.val(), $this); - } - } else { - // kval postcond - ($this.val() > opt.max && $this.val(opt.max)) - || ($this.val() < opt.min && $this.val(opt.min)); - } - - } - ) - .bind( - "mousewheel DOMMouseScroll" - ,mw - ); - } else { - $this.attr('readonly', 'readonly'); - } + if (this.o.displayPrevious) { + pa = this.arc(this.v); + c.beginPath(); + c.strokeStyle = this.pColor; + c.arc(this.xy, this.xy, this.radius, pa.s, pa.e, pa.d); + c.stroke(); + r = this.cv == this.v; + } + + c.beginPath(); + c.strokeStyle = r ? this.o.fgColor : this.fgColor ; + c.arc(this.xy, this.xy, this.radius, a.s, a.e, a.d); + c.stroke(); + }; + + this.cancel = function () { + this.val(this.v); + }; + }; + + $.fn.dial = $.fn.knob = function (o) { + return this.each( + function () { + var d = new k.Dial(); + d.o = o; + d.$ = $(this); + d.run(); } ).parent(); }; - function degreeToRadians (angle) { - return $.isNumeric(angle) ? angle * Math.PI / 180 : 0; - } -}); \ No newline at end of file +})); diff --git a/knob.jquery.json b/knob.jquery.json new file mode 100755 index 0000000..26564ad --- /dev/null +++ b/knob.jquery.json @@ -0,0 +1,37 @@ +{ + "name": "knob", + "title": "jQuery Knob", + "description": "Nice, downward compatible, touchable, jQuery dial.", + "keywords": [ + "dial", + "button", + "knob", + "ui", + "input" + ], + "version": "1.2.11", + "author": { + "name": "Anthony Terrien", + "url": "https://github.com/aterrien" + }, + "maintainers": [ + { + "name": "Anthony Terrien", + "email": "kontrol@anthonyterrien.com", + "url": "http://anthonyterrien.com/knob" + } + ], + "licenses": [ + { + "type": "MIT", + "url": "http://opensource.org/licenses/mit-license.php" + } + ], + "bugs": "https://github.com/aterrien/jQuery-Knob/issues", + "homepage": "https://github.com/aterrien/jQuery-Knob", + "docs": "https://github.com/aterrien/jQuery-Knob", + "download": "https://github.com/aterrien/jQuery-Knob/tags", + "dependencies": { + "jquery": ">=1.7.0" + } +} diff --git a/secretplan.jpg b/secretplan.jpg new file mode 100755 index 0000000000000000000000000000000000000000..e6f98dece5e1be83898eeefd08cde56fffe61b25 GIT binary patch literal 33487 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 ZkoD Date: Fri, 12 Dec 2014 18:13:36 +0100 Subject: [PATCH 10/12] added classes to canvas and container objects --- js/jquery.knob.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/js/jquery.knob.js b/js/jquery.knob.js index 5294ffa..eaa943e 100644 --- a/js/jquery.knob.js +++ b/js/jquery.knob.js @@ -184,12 +184,13 @@ // adds needed DOM elements (canvas, div) this.$c = $(document.createElement('canvas')).attr({ width: this.o.width, - height: this.o.height + height: this.o.height, + class: "knobCanvas" }); // wraps all elements in a div // add to DOM before Canvas init is triggered - this.$div = $('
'); From e03fe0b5b7c5b42b1edcf30b0d84fe33d95ecfc0 Mon Sep 17 00:00:00 2001 From: FabrizioC Date: Fri, 12 Dec 2014 18:50:46 +0100 Subject: [PATCH 11/12] Triggering change hook on mousewheel and keyboard --- js/jquery.knob.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/js/jquery.knob.js b/js/jquery.knob.js index eaa943e..ec7a886 100644 --- a/js/jquery.knob.js +++ b/js/jquery.knob.js @@ -581,6 +581,8 @@ v = max(min(v, s.o.max), s.o.min); + if (s.cH && s.cH(v) === false) return; + s.val(v, false); if (s.rH) { @@ -640,6 +642,8 @@ var v = s.o.parse(s.$.val()) + kv[kc] * m; s.o.stopper && (v = max(min(v, s.o.max), s.o.min)); + if (s.cH && s.cH(v) === false) return; + s.change(s._validate(v)); s._draw(); @@ -648,6 +652,11 @@ m *= 2; }, 30); } + else if (kc == 13) { + var v = s.o.parse(s.$.val()); + s.o.stopper && (v = max(min(v, s.o.max), s.o.min)); + if (s.cH && s.cH(v) === false) return; + } } } ) From d9db487ea1297ee8fe9635afd392f253459e541a Mon Sep 17 00:00:00 2001 From: FabrizioC Date: Wed, 17 Dec 2014 11:06:44 +0100 Subject: [PATCH 12/12] fixed displayInput size on external format --- js/jquery.knob.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/js/jquery.knob.js b/js/jquery.knob.js index ec7a886..f080a80 100644 --- a/js/jquery.knob.js +++ b/js/jquery.knob.js @@ -709,10 +709,10 @@ this.endAngle = 1.5 * Math.PI + this.angleOffset + this.angleArc; var s = max( - String(Math.abs(this.o.max)).length, - String(Math.abs(this.o.min)).length, - 2 - ) + 2; + this.o.format(this.o.max).length || String(Math.abs(this.o.max)).length + 2, + this.o.format(this.o.min).length || String(Math.abs(this.o.min)).length + 2, + 4 + ); this.o.displayInput && this.i.css({