Playing with sliders and knobs

Just as an experiment, I thought sliders may work well with SVG & Snap. Graphics are just using existing clipart to test.
Note, there is a bug on knobs if zoomed/scrolled in browser, so this is currently only a work in progress



(function() {

  Snap.plugin( function( Snap, Element, Paper, global ) {
        var startDragTarget, startDragElement, startBBox, startScreenCTM;

        // Initialise our slider with its basic transform and drag funcs

        Element.prototype.initSlider = function( params ) {
                        var emptyFunc = function() {};
                        this.data('origTransform', this.transform().local );
                        this.data('onDragEndFunc', params.onDragEndFunc || emptyFunc );
                        this.data('onDragFunc', params.onDragFunc || emptyFunc );
                        this.data('onDragStartFunc', params.onDragStartFunc || emptyFunc );
                }

        // initialise the params, and set up our max and min. Check if its a slider or knob to see how we deal

        Element.prototype.sliderAnyAngle = function( params ) {
                this.initSlider( params );
                this.data("maxPosX", params.max); this.data("minPosX", params.min);
                this.data("centerOffsetX", params.centerOffsetX); this.data("centerOffsetY", params.centerOffsetY)
                this.data("posX", params.min);
                if( params.type == 'knob' ) {
                        this.drag( moveDragKnob, startDrag, endDrag );
                } else {
                        this.drag( moveDragSlider, startDrag, endDrag );
                }
        }

        // load in the slider svg file, and transform the group element according to our params earlier.
        // Also choose which id is the cap

        Paper.prototype.slider = function( params ) {
                var myPaper = this,  myGroup;
                var loaded = Snap.load( params.filename, function( frag ) {
                                myGroup = myPaper.group().add( frag );
                                myGroup.transform("t" + params.x + "," + params.y);
                                var myCap = myGroup.select( params.capSelector );
                                myCap.data("sliderId", params.sliderId);
                                myCap.sliderAnyAngle( params );
                                sliderSetAttributes( myGroup, params.attr );
                                sliderSetAttributes( myCap, params.capattr );
                        } );
                return myGroup;
        }

       // Extra func, to pass through extra attributes passed when creating the slider

        function sliderSetAttributes ( myGroup, attr, data ) {
                var myObj = {};
                if( typeof attr != 'undefined' ) {
                        for( var prop in attr ) {
                                myObj[ prop ] = attr[prop];
                                myGroup.attr( myObj );
                                myObj = {};
                        };
                };
        };

        // Our main slider startDrag, store our initial matrix settings. 

        var startDrag = function( x, y, ev ) {
                startDragTarget = ev.target;
                if( ! ( this.data("startBBox") ) ) {
                        this.data("startBBox", this.getBBox());
                        this.data("startScreenCTM",startDragTarget.getScreenCTM());
                }
                this.data('origPosX', this.data("posX") ); this.data('origPosY', this.data("posY") );
                this.data("onDragStartFunc")();
        }


        // move the cap, our dx/dy will need to be transformed to element matrx. Test for min/max
        // set a value 'fracX' which is a fraction of amount moved 0-1 we can use later.

        function updateMovement( el, dx, dy ) {
                // Below relies on parent being the file svg element, 9
                var snapInvMatrix = el.parent().transform().globalMatrix.invert();
                snapInvMatrix.e = snapInvMatrix.f = 0;
                var tdx = snapInvMatrix.x( dx,dy ), tdy = snapInvMatrix.y( dx,dy );

                el.data("posX", +el.data("origPosX") + tdx) ; el.data("posY", +el.data("origPosY") + tdy);
                var posX = +el.data("posX"); //var posY = +el.data("posY");
                var maxPosX = +el.data("maxPosX");
                var minPosX = +el.data("minPosX");

                if( posX > maxPosX ) { el.data("posX", maxPosX ); };
                if( posX < minPosX ) { el.data("posX", minPosX ); };
                el.data("fracX", 1/ ( (maxPosX - minPosX) / el.data("posX") ) );
        }


        // Call the matrix checks above, and set any transformation

        function moveDragSlider( dx,dy ) {
                var posX;
                updateMovement( this, dx, dy );
                posX = this.data("posX");
                this.attr({ transform: this.data("origTransform") + (posX ? "T" : "t") + [posX,0] });
                this.data("onDragFunc")(this);
        };

        // drag our knob. Currently there is no min/max working, need to add a case for testing rotating anticlockwise beyond 0

        function moveDragKnob( dx,dy,x,y, ev ) {
                var pnt = startDragTarget.ownerSVGElement.createSVGPoint();
                pnt.x = ev.clientX; pnt.y = ev.clientY;
                var vPnt = pnt.matrixTransform(this.data("startScreenCTM").inverse());
                var transformRequestObj = startDragTarget.ownerSVGElement.createSVGTransform();

                var deg = Math.atan2(vPnt.x - this.data("startBBox").cx, vPnt.y - this.data("startBBox").cy) * 180 / Math.PI ;
                deg = deg + 180;
//              this.transform('r' + -deg + "," + ( this.data("startBBox").cx - 4 ) + "," + parseInt(this.data("startBBox").cy - -8)  );
                this.transform('r' + -deg + "," + ( this.data("startBBox").cx - this.data("centerOffsetX") ) + "," + parseInt(this.data("startBBox").cy - -this.data("centerOffsetY") )  );
                this.data("fracX", deg/360);
                this.data("onDragFunc")(this);
        }



        function endDrag() {
                this.data('onDragEndFunc')();
        };

  });

})();



//// Test code using the above funcs...
//http://jsfiddle.net/Ge8Eu/

var s = Snap("#svgout");

var myDragEndFunc = function( el ) {
}

var myDragStartFunc = function() {
}


// what we want to do when the slider changes. They could have separate funcs as the call back or just pick the right element
var myDragFunc = function( el ) {
        if( el.data("sliderId") == "slider1" ) { meter1.attr({ height: el.data("fracX") * 200 }); }
        if( el.data("sliderId") == "slider2" ) { meter2.attr({ height: el.data("fracX") * 200 }); }
        if( el.data("sliderId") == "slider3" ) { meter3.attr({ height: el.data("fracX") * 200 }); }
        if( el.data("sliderId") == "slider4" ) { meter4.attr({ height: el.data("fracX") * 200 }); }
        if( el.data("sliderId") == "knob1" )   { meter5.attr({ height: el.data("fracX") * 200 }); }
}

// just some shapes/data we will move when the knob or slider moves
var meter1 = s.rect( 300,300,50,5 ).attr({ fill: "yellow", stroke: "black", strokeWidth: 2 }).transform('r180');
var meter2 = s.rect( 250,300,50,5 ).attr({ fill: "blue", stroke: "black", strokeWidth: 2 }).transform('r180');
var meter3 = s.rect( 350,300,50,5 ).attr({ fill: "green", stroke: "black", strokeWidth: 2 }).transform('r180');
var meter4 = s.rect( 400,300,50,5 ).attr({ fill: "red", stroke: "black", strokeWidth: 2 }).transform('r180');
var meter5 = s.rect( 450,300,50,5 ).attr({ fill: "silver", stroke: "black", strokeWidth: 2 }).transform('r180');


//create our sliders
var slider1 = s.slider({ sliderId: "slider1", capSelector: "#cap", filename: "paw.svg",
                x: "40", y:"240", min: "0", max: "800",
                onDragEndFunc: myDragEndFunc, onDragStartFunc: myDragStartFunc, onDragFunc: myDragFunc,
                attr: { transform: 's0.4t-100,-300' } } );

var slider2 = s.slider({ sliderId: "slider2", capSelector: "#cap", filename: "slider2.svg",
                x: "40", y:"200", min: "0", max: "400",
                onDragEndFunc: myDragEndFunc, onDragStartFunc: myDragStartFunc, onDragFunc: myDragFunc } );

var sliderOnAngle = s.slider({ sliderId: "slider3", capSelector: "#cap", filename: "slider2.svg",
                x: "140", y:"150", min: "0", max: "400",
                onDragEndFunc: myDragEndFunc, onDragStartFunc: myDragStartFunc, onDragFunc: myDragFunc,
                attr: { transform: 't150,150r45' } });

var sliderInverted = s.slider({ sliderId: "slider4", capSelector: "#cap", filename: "slider2.svg",
                x: "200", y:"200", min: "0", max: "400",
                onDragEndFunc: myDragEndFunc, onDragStartFunc: myDragStartFunc, onDragFunc: myDragFunc,
                attr: { transform: 't50,150r180' } });

var knob = s.slider({ sliderId: "knob1", type: "knob", capSelector: "#cap", filename: "knob1.svg",
                x: "140", y:"50", min: "30", max: "340", centerOffsetX: "4", centerOffsetY: "8",
                onDragEndFunc: myDragEndFunc, onDragStartFunc: myDragStartFunc, onDragFunc: myDragFunc,
                attr: { transform: 's0.3' } });// , capattr: { transform: 't0,0' } });

   

        
The actual svg markup looks like this (when you've clicked on run)....