From d215acef78eeecfa1b2646576c9c141f883b00ad Mon Sep 17 00:00:00 2001 From: Michael Bostock Date: Fri, 11 Feb 2011 13:00:41 -0800 Subject: [PATCH] Add d3.behavior module. Includes, as the first behavior, a pan & zoom behavior. The canvas can be panned by dragging the mouse, and zoomed using the mousewheel (or by double-click). By listening to redraw events, users can decide whether to implement geometric zooming (such as by setting the "transform" attribute on an `svg:g` element) or semantic zooming (by changing the domain of a scale object and repositioning elements). This commit also includes two bug fixes. The `d3.format` class now properly groups thousands of negative numbers, and supports the sign specifier. The unicode minus symbol \u2212 is used for negative values. The `d3.scale.pow` class now properly handles negative numbers, as well. --- Makefile | 8 ++ d3.behavior.js | 132 ++++++++++++++++++++++++++++++++ d3.behavior.min.js | 3 + d3.js | 27 ++++--- d3.min.js | 119 ++++++++++++++-------------- examples/splom/splom.html | 21 +++-- examples/stream/stream.html | 5 +- examples/zoom-pan/zoom-pan.html | 114 +++++++++++++++++++++++++++ src/behavior/behavior.js | 1 + src/behavior/zoom.js | 130 +++++++++++++++++++++++++++++++ src/core/core.js | 2 +- src/core/format.js | 21 +++-- src/scale/pow.js | 4 +- 13 files changed, 495 insertions(+), 92 deletions(-) create mode 100644 d3.behavior.js create mode 100644 d3.behavior.min.js create mode 100644 examples/zoom-pan/zoom-pan.html create mode 100644 src/behavior/behavior.js create mode 100644 src/behavior/zoom.js diff --git a/Makefile b/Makefile index 8891d9d1..f5d14362 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,8 @@ JS_COMPILER = \ all: \ d3.js \ d3.min.js \ + d3.behavior.js \ + d3.behavior.min.js \ d3.layout.js \ d3.layout.min.js \ d3.csv.js \ @@ -81,6 +83,12 @@ d3.svg.js: \ src/svg/chord.js \ src/svg/mouse.js +d3.behavior.js: \ + src/start.js \ + src/behavior/behavior.js \ + src/behavior/zoom.js \ + src/end.js + d3.layout.js: \ src/start.js \ src/layout/layout.js \ diff --git a/d3.behavior.js b/d3.behavior.js new file mode 100644 index 00000000..44a92779 --- /dev/null +++ b/d3.behavior.js @@ -0,0 +1,132 @@ +(function(){d3.behavior = {}; +// TODO unbind zoom behavior? +// TODO unbind listener? +d3.behavior.zoom = function() { + + // https://bugs.webkit.org/show_bug.cgi?id=40441 + var bug40441 = /WebKit\/533/.test(navigator.userAgent) ? -1 : 0, + bug40441Last = 0, + x = 0, + y = 0, + z = 0, + listeners = [], + pan, + zoom; + + function zoom() { + var container = this + .on("mousedown", mousedown) + .on("mousewheel", mousewheel) + .on("DOMMouseScroll", mousewheel) + .on("dblclick", mousewheel); + + d3.select(window) + .on("mousemove", mousemove) + .on("mouseup", mouseup); + } + + function mousedown(d, i) { + pan = { + x0: x - d3.event.clientX, + y0: y - d3.event.clientY, + target: this, + data: d, + index: i + }; + d3.event.preventDefault(); + window.focus(); // TODO focusableParent + } + + function mousemove() { + zoom = null; + if (pan) { + x = d3.event.clientX + pan.x0; + y = d3.event.clientY + pan.y0; + dispatch.call(pan.target, pan.data, pan.index); + } + } + + function mouseup() { + if (pan) { + mousemove(); + pan = null; + } + } + + function mousewheel(d, i) { + var e = d3.event; + + // initialize the mouse location for zooming (to avoid drift) + if (!zoom) { + var p = d3.svg.mouse(this.nearestViewportElement || this); + zoom = { + x0: x, + y0: y, + z0: z, + x1: x - p[0], + y1: y - p[1] + }; + } + + // adjust zoom level + if (e.type == "dblclick") { + z = e.shiftKey ? Math.ceil(z - 1) : Math.floor(z + 1); + } else { + var delta = (e.wheelDelta / 120 || -e.detail) * .1; + + /* Detect fast & large wheel events on WebKit. */ + if (bug40441 < 0) { + var now = Date.now(), since = now - bug40441Last; + if ((since > 9) && (Math.abs(e.wheelDelta) / since >= 50)) bug40441 = 1; + bug40441Last = now; + } + if (bug40441 == 1) delta *= .03; + + z += delta; + } + + // adjust x and y to center around mouse location + var k = Math.pow(2, z - zoom.z0) - 1; + x = zoom.x0 + zoom.x1 * k; + y = zoom.y0 + zoom.y1 * k; + + // dispatch redraw + dispatch.call(this, d, i); + } + + function dispatch(d, i) { + var o = d3.event, // Events can be reentrant (e.g., focus). + k = Math.pow(2, z); + + d3.event = { + scale: k, + translate: [x, y], + transform: function(sx, sy) { + if (sx) transform(sx, x); + if (sy) transform(sy, y); + } + }; + + function transform(scale, o) { + var domain = scale.__domain || (scale.__domain = scale.domain()); + range = scale.range().map(function(v) { return (v - o) / k; }); + scale.domain(domain).domain(range.map(scale.invert)); + } + + try { + for (var j = 0, m = listeners.length; j < m; j++) { + listeners[j].call(this, d, i); + } + } finally { + d3.event = o; + } + } + + zoom.on = function(type, listener) { + if (type == "zoom") listeners.push(listener); + return zoom; + }; + + return zoom; +}; +})() \ No newline at end of file diff --git a/d3.behavior.min.js b/d3.behavior.min.js new file mode 100644 index 00000000..6cab6351 --- /dev/null +++ b/d3.behavior.min.js @@ -0,0 +1,3 @@ +(function(){d3.behavior={};d3.behavior.zoom=function(){function a(){this.on("mousedown",t).on("mousewheel",n).on("DOMMouseScroll",n).on("dblclick",n);d3.select(window).on("mousemove",q).on("mouseup",u)}function t(e,f){c={x0:g-d3.event.clientX,y0:h-d3.event.clientY,target:this,data:e,index:f};d3.event.preventDefault();window.focus()}function q(){a=null;if(c){g=d3.event.clientX+c.x0;h=d3.event.clientY+c.y0;r.call(c.target,c.data,c.index)}}function u(){if(c){q();c=null}}function n(e,f){var b=d3.event; +if(!a){var i=d3.svg.mouse(this.nearestViewportElement||this);a={x0:g,y0:h,z0:j,x1:g-i[0],y1:h-i[1]}}if(b.type=="dblclick")j=b.shiftKey?Math.ceil(j-1):Math.floor(j+1);else{i=(b.wheelDelta/120||-b.detail)*0.1;if(o<0){var l=Date.now(),k=l-s;if(k>9&&Math.abs(b.wheelDelta)/k>=50)o=1;s=l}if(o==1)i*=0.03;j+=i}b=Math.pow(2,j-a.z0)-1;g=a.x0+a.x1*b;h=a.y0+a.y1*b;r.call(this,e,f)}function r(e,f){function b(d,m){var v=d.__domain||(d.__domain=d.domain());range=d.range().map(function(w){return(w-m)/l});d.domain(v).domain(range.map(d.invert))} +var i=d3.event,l=Math.pow(2,j);d3.event={scale:l,translate:[g,h],transform:function(d,m){d&&b(d,g);m&&b(m,h)}};try{for(var k=0,x=p.length;k= 0 ? value.substring(i) : (i = value.length, ""), @@ -321,14 +324,20 @@ d3.format = function(specifier) { while (i > 0) t.push(value.substring(i -= 3, i + 3)); value = t.reverse().join(",") + f; } - var n = value.length; - if (n < width) value = new Array(width - n + 1).join(fill) + value; + var length = (value = sign(negative, value)).length; + if (length < width) value = new Array(width - length + 1).join(fill) + value; return value; }; }; // [[fill]align][sign][#][0][width][,][.precision][type] var d3_format_re = /(?:([^{])?([<>=^]))?([+\- ])?(#)?(0)?([0-9]+)?(,)?(\.[0-9]+)?([a-zA-Z%])?/; + +var d3_format_signs = { + "+": function(negative, value) { return (negative ? "\u2212" : "+") + value; }, + " ": function(negative, value) { return (negative ? "\u2212" : " ") + value; }, + "-": function(negative, value) { return negative ? "\u2212" + value : value; } +}; /* * TERMS OF USE - EASING EQUATIONS * @@ -1903,11 +1912,11 @@ d3.scale.pow = function() { b = 1 / p; function powp(x) { - return Math.pow(x, p); + return x < 0 ? -Math.pow(-x, p) : Math.pow(x, p); } function powb(x) { - return Math.pow(x, b); + return x < 0 ? -Math.pow(-x, b) : Math.pow(x, b); } function scale(x) { diff --git a/d3.min.js b/d3.min.js index ae9b933f..d0110f15 100644 --- a/d3.min.js +++ b/d3.min.js @@ -1,62 +1,63 @@ -(function(){function Q(a){return Array.prototype.slice.call(a)}function x(a){return typeof a=="function"?a:function(){return a}}function D(a,b){return function(){var c=b.apply(a,arguments);return arguments.length?a:c}}function qa(a){return a==null}function ca(a){return a.replace(/(^\s+)|(\s+$)/g,"").replace(/\s+/g," ")}function da(a,b){b=Q(arguments);b[0]=this;a.apply(this,b);return this}function ra(){var a={},b=[];a.add=function(c){for(var g=0;g360)h-=360;else if(h<0)h+=360;if(h<60)return e+(d-e)*h/60;if(h<180)return d;if(h<240)return e+(d-e)*(240-h)/60;return e} -var e,d;a%=360;if(a<0)a+=360;b=b<0?0:b>1?1:b;c=c<0?0:c>1?1:c;d=c<=0.5?c*(1+b):c+b-c*b;e=2*c-d;return J(Math.round(g(a+120)*255),Math.round(g(a)*255),Math.round(g(a-120)*255))}function y(a){function b(e){for(var d=[],h,f,i,k,j=0,o=a.length;j360)i-=360;else if(i<0)i+=360;if(i<60)return e+(d-e)*i/60;if(i<180)return d;if(i<240)return e+(d-e)*(240-i)/60;return e} +var e,d;a%=360;if(a<0)a+=360;b=b<0?0:b>1?1:b;c=c<0?0:c>1?1:c;d=c<=0.5?c*(1+b):c+b-c*b;e=2*c-d;return J(Math.round(g(a+120)*255),Math.round(g(a)*255),Math.round(g(a-120)*255))}function y(a){function b(e){for(var d=[],i,f,h,k,j=0,o=a.length;jg){i[q]= -2;return}else{i[q]=1;f.start.dispatch.apply(this,arguments);u=d[q]={};r.active=g;for(s in e)u[s]=e[s].apply(this,arguments)}t=p(m);for(s in e)u[s].call(this,t);if(m==1){i[q]=2;if(r.active==g){m=r.owner;if(m==g){delete this.__transition__;h&&this.parentNode.removeChild(this)}X=g;f.end.dispatch.apply(this,arguments);X=0;r.owner=m}}}});return n}var c={},g=X||++Da,e={},d=[],h=false,f=d3.dispatch("start","end"),i=[],k=[],j=[],o,p=d3.ease("cubic-in-out");a.each(function(){(this.__transition__||(this.__transition__= -{})).owner=g});c.delay=function(l){var n=Infinity,q=-1;if(typeof l=="function")a.each(function(){var m=k[++q]=+l.apply(this,arguments);if(mo)o=q})}else{o=+l;a.each(function(){j[++n]=o})}return c};c.ease=function(l){p=typeof l=="string"?d3.ease(l):l;return c};c.attrTween=function(l,n){function q(r,t){var s=n.call(this, +z.parentData=v.parentData=l.parentData;f.push(A);h.push(z);k.push(v)}var f=[],h=[],k=[],j=-1,o=a.length,p;if(typeof e=="function")for(;++jg){h[q]= +2;return}else{h[q]=1;f.start.dispatch.apply(this,arguments);u=d[q]={};r.active=g;for(s in e)u[s]=e[s].apply(this,arguments)}t=p(m);for(s in e)u[s].call(this,t);if(m==1){h[q]=2;if(r.active==g){m=r.owner;if(m==g){delete this.__transition__;i&&this.parentNode.removeChild(this)}X=g;f.end.dispatch.apply(this,arguments);X=0;r.owner=m}}}});return n}var c={},g=X||++Ea,e={},d=[],i=false,f=d3.dispatch("start","end"),h=[],k=[],j=[],o,p=d3.ease("cubic-in-out");a.each(function(){(this.__transition__||(this.__transition__= +{})).owner=g});c.delay=function(l){var n=Infinity,q=-1;if(typeof l=="function")a.each(function(){var m=k[++q]=+l.apply(this,arguments);if(mo)o=q})}else{o=+l;a.each(function(){j[++n]=o})}return c};c.ease=function(l){p=typeof l=="string"?d3.ease(l):l;return c};c.attrTween=function(l,n){function q(r,t){var s=n.call(this, r,t,this.getAttribute(l));return function(u){this.setAttribute(l,s(u))}}function m(r,t){var s=n.call(this,r,t,this.getAttributeNS(l.space,l.local));return function(u){this.setAttributeNS(l.space,l.local,s(u))}}e["attr."+l]=l.local?m:q;return c};c.attr=function(l,n){return c.attrTween(l,ha(n))};c.styleTween=function(l,n,q){if(arguments.length<3)q=null;e["style."+l]=function(m,r){var t=n.call(this,m,r,window.getComputedStyle(this,null).getPropertyValue(l));return function(s){this.style.setProperty(l, -t(s),q)}};return c};c.style=function(l,n,q){if(arguments.length<3)q=null;return c.styleTween(l,ha(n),q)};c.select=function(l){var n;l=W(a.select(l)).ease(p);n=-1;l.delay(function(){return k[++n]});n=-1;l.duration(function(){return j[++n]});return l};c.selectAll=function(l){var n;l=W(a.selectAll(l)).ease(p);n=-1;l.delay(function(q,m){return k[m?n:++n]});n=-1;l.duration(function(q,m){return j[m?n:++n]});return l};c.remove=function(){h=true;return c};c.each=function(l,n){f[l].add(n);return c};c.call= -da;return c.delay(0).duration(250)}function ha(a){return typeof a=="function"?function(b,c,g){return d3.interpolate(g,String(a.call(this,b,c)))}:(a=String(a),function(b,c,g){return d3.interpolate(g,a)})}function Ea(a,b){var c=Date.now(),g=false,e=c+b,d=F;if(isFinite(b)){for(;d;){if(d.callback==a){d.then=c;d.delay=b;g=true}else{var h=d.then+d.delay;if(hc.delay)c.flush=c.callback(a);c=c.next}a=null;for(b=F;b;)b=b.flush?a?a.next=b.next:F=b.next:(a=b).next;a||(K=clearInterval(K))}function Ha(a){return a.innerRadius}function Ia(a){return a.outerRadius}function ia(a){return a.startAngle}function ja(a){return a.endAngle}function Z(a,b,c,g){var e=[],d=-1,h=b.length,f=typeof c=="function",i=typeof g=="function",k;if(f&&i)for(;++d1){f=b[1];d=a[i];i++;g+="C"+(e[0]+h[0])+","+(e[1]+h[1])+","+(d[0]-f[0])+","+(d[1]-f[1])+","+d[0]+","+d[1];for(e=2;eb?1:0};d3.descending=function(a, -b){return ba?1:0};d3.min=function(a,b){var c=0,g=a.length,e=a[0],d;if(arguments.length==1)for(;++c(d=a[c]))e=d}else for(e=b(a[0]);++c(d=b(a[c])))e=d;return e};d3.max=function(a,b){var c=0,g=a.length,e=a[0],d;if(arguments.length==1)for(;++c=c.length)return e?e.call(b,h):g?h.sort(g):h;for(var f=-1,i=h.length,k=c[d],j,o=[],p,l={};++fb;)g.push(d);else for(;(d=a+c*++e)c.delay)c.flush=c.callback(a);c=c.next}a=null;for(b=F;b;)b=b.flush?a?a.next=b.next:F=b.next:(a=b).next;a||(K=clearInterval(K))}function Ia(a){return a.innerRadius}function Ja(a){return a.outerRadius}function ia(a){return a.startAngle}function ja(a){return a.endAngle}function Z(a,b,c,g){var e=[],d=-1,i=b.length,f=typeof c=="function",h=typeof g=="function",k;if(f&&h)for(;++d1){f=b[1];d=a[h];h++;g+="C"+(e[0]+i[0])+","+(e[1]+i[1])+","+(d[0]-f[0])+","+(d[1]-f[1])+","+d[0]+","+d[1];for(e=2;eb?1:0};d3.descending=function(a, +b){return ba?1:0};d3.min=function(a,b){var c=0,g=a.length,e=a[0],d;if(arguments.length==1)for(;++c(d=a[c]))e=d}else for(e=b(a[0]);++c(d=b(a[c])))e=d;return e};d3.max=function(a,b){var c=0,g=a.length,e=a[0],d;if(arguments.length==1)for(;++c=c.length)return e?e.call(b,i):g?i.sort(g):i;for(var f=-1,h=i.length,k=c[d],j,o=[],p,l={};++fb;)g.push(d);else for(;(d=a+c*++e)=0?f.substring(i):(i=f.length,""),j=[];i>0;)j.push(f.substring(i-=3,i+3));f=j.reverse().join(",")+k}i=f.length;if(i=^]))?([+\- ])?(#)?(0)?([0-9]+)?(,)?(\.[0-9]+)?([a-zA-Z%])?/,Pa=R(2),Qa=R(3),Ra={linear:function(){return sa},poly:R,quad:function(){return Pa},cubic:function(){return Qa},sin:function(){return ta},exp:function(){return ua},circle:function(){return va},elastic:function(a,b){var c;if(arguments.length<2)b=0.45;if(arguments.length<1){a=1;c=b/4}else c=b/(2*Math.PI)*Math.asin(1/a);return function(g){return 1+a*Math.pow(2,10*-g)*Math.sin((g-c)*2*Math.PI/ -b)}},back:function(a){a||(a=1.70158);return function(b){return b*b*((a+1)*b-a)}},bounce:function(){return wa}},Sa={"in":function(a){return a},out:ea,"in-out":fa,"out-in":function(a){return fa(ea(a))}};d3.ease=function(a){var b=a.indexOf("-"),c=b>=0?a.substring(0,b):a;b=b>=0?a.substring(b+1):"in";return Sa[b](Ra[c].apply(null,Array.prototype.slice.call(arguments,1)))};d3.event=null;d3.interpolate=function(a,b){if(typeof b=="number")return d3.interpolateNumber(+a,b);if(typeof b=="string")return b in -G||/^(#|rgb\(|hsl\()/.test(b)?d3.interpolateRgb(String(a),b):d3.interpolateString(String(a),b);if(b instanceof Array)return d3.interpolateArray(a,b);return d3.interpolateObject(a,b)};d3.interpolateNumber=function(a,b){b-=a;return function(c){return a+b*c}};d3.interpolateRound=function(a,b){b-=a;return function(c){return Math.round(a+b*c)}};d3.interpolateString=function(a,b){var c,g,e=0,d=[],h=[],f,i;for(g=0;c=$.exec(b);++g){c.index&&d.push(b.substring(e,c.index));h.push({i:d.length,x:c[0]});d.push(null); -e=$.lastIndex}e>1,j=d[k];if(jh)i=k-1;else return k}return i<0?0:i}function c(h){return e[b(h)]}var g=[],e=[],d=[];c.domain=function(h){if(!arguments.length)return g;g=h.filter(function(f){return!isNaN(f)}).sort(d3.ascending);a();return c};c.range=function(h){if(!arguments.length)return e;e=h;a();return c};c.quantiles=function(){return d};return c};d3.scale.quantize=function(){function a(h){return d[Math.max(0,Math.min(e,Math.floor(g*(h-b))))]}var b=0,c=1,g=2,e=1,d=[0,1];a.domain=function(h){if(!arguments.length)return[b, -c];b=h[0];c=h[1];g=d.length/(c-b);return a};a.range=function(h){if(!arguments.length)return d;d=h;g=d.length/(c-b);e=d.length-1;return a};return a};d3.svg={};d3.svg.arc=function(){function a(d,h){var f=b.call(this,d,h),i=c.call(this,d,h),k=g.call(this,d,h)+O,j=e.call(this,d,h)+O,o=j-k,p=o=Ya?f?"M0,"+i+"A"+i+","+i+" 0 1,1 0,"+-i+"A"+i+","+i+" 0 1,1 0,"+i+"M0,"+f+"A"+f+","+f+" 0 1,1 0,"+-f+"A"+f+","+f+" 0 1,1 0,"+ -f+"Z":"M0,"+i+"A"+i+","+i+" 0 1,1 0,"+-i+"A"+i+","+i+" 0 1,1 0,"+i+"Z":f?"M"+i*l+","+i*k+"A"+i+","+i+" 0 "+p+",1 "+i*n+","+i*j+"L"+f*n+","+f*j+"A"+f+","+f+" 0 "+p+",0 "+f*l+","+f*k+"Z":"M"+i*l+","+i*k+"A"+i+","+i+" 0 "+p+",1 "+i*n+","+i*j+"L0,0Z"}var b=Ha,c=Ia,g=ia,e=ja;a.innerRadius=function(d){if(!arguments.length)return b;b=x(d);return a};a.outerRadius=function(d){if(!arguments.length)return c;c=x(d);return a};a.startAngle=function(d){if(!arguments.length)return g;g=x(d);return a};a.endAngle=function(d){if(!arguments.length)return e; -e=x(d);return a};return a};var O=-Math.PI/2,Ya=2*Math.PI-1.0E-6;d3.svg.line=function(){function a(h){return h.length<1?null:"M"+e(Z(this,h,b,c),d)}var b=ka,c=la,g="linear",e=P[g],d=0.7;a.x=function(h){if(!arguments.length)return b;b=h;return a};a.y=function(h){if(!arguments.length)return c;c=h;return a};a.interpolate=function(h){if(!arguments.length)return g;e=P[g=h];return a};a.tension=function(h){if(!arguments.length)return d;d=h;return a};return a};var P={linear:I,basis:function(a){if(a.length< -3)return I(a);var b=[],c=1,g=a.length,e=a[0],d=e[0],h=e[1],f=[d,d,d,(e=a[1])[0]],i=[h,h,h,e[1]];b.push(d,",",h);for(L(b,f,i);++c=0?h.substring(j):(j=h.length,""),p=[];j>0;)p.push(h.substring(j-=3,j+3));h=p.reverse().join(",")+ +o}k=(h=c(k,h)).length;if(k=^]))?([+\- ])?(#)?(0)?([0-9]+)?(,)?(\.[0-9]+)?([a-zA-Z%])?/,qa={"+":function(a,b){return(a?"−":"+")+b}," ":function(a,b){return(a?"−":" ")+b},"-":function(a,b){return a?"−"+b:b}},Qa=R(2),Ra=R(3),Sa={linear:function(){return ta},poly:R,quad:function(){return Qa},cubic:function(){return Ra},sin:function(){return ua},exp:function(){return va},circle:function(){return wa},elastic:function(a,b){var c;if(arguments.length< +2)b=0.45;if(arguments.length<1){a=1;c=b/4}else c=b/(2*Math.PI)*Math.asin(1/a);return function(g){return 1+a*Math.pow(2,10*-g)*Math.sin((g-c)*2*Math.PI/b)}},back:function(a){a||(a=1.70158);return function(b){return b*b*((a+1)*b-a)}},bounce:function(){return xa}},Ta={"in":function(a){return a},out:ea,"in-out":fa,"out-in":function(a){return fa(ea(a))}};d3.ease=function(a){var b=a.indexOf("-"),c=b>=0?a.substring(0,b):a;b=b>=0?a.substring(b+1):"in";return Ta[b](Sa[c].apply(null,Array.prototype.slice.call(arguments, +1)))};d3.event=null;d3.interpolate=function(a,b){if(typeof b=="number")return d3.interpolateNumber(+a,b);if(typeof b=="string")return b in G||/^(#|rgb\(|hsl\()/.test(b)?d3.interpolateRgb(String(a),b):d3.interpolateString(String(a),b);if(b instanceof Array)return d3.interpolateArray(a,b);return d3.interpolateObject(a,b)};d3.interpolateNumber=function(a,b){b-=a;return function(c){return a+b*c}};d3.interpolateRound=function(a,b){b-=a;return function(c){return Math.round(a+b*c)}};d3.interpolateString= +function(a,b){var c,g,e=0,d=[],i=[],f,h;for(g=0;c=$.exec(b);++g){c.index&&d.push(b.substring(e,c.index));i.push({i:d.length,x:c[0]});d.push(null);e=$.lastIndex}e>1,j=d[k];if(ji)h=k-1;else return k}return h<0?0:h}function c(i){return e[b(i)]}var g=[],e=[],d=[];c.domain=function(i){if(!arguments.length)return g;g=i.filter(function(f){return!isNaN(f)}).sort(d3.ascending);a();return c};c.range=function(i){if(!arguments.length)return e;e=i;a();return c};c.quantiles=function(){return d};return c};d3.scale.quantize=function(){function a(i){return d[Math.max(0, +Math.min(e,Math.floor(g*(i-b))))]}var b=0,c=1,g=2,e=1,d=[0,1];a.domain=function(i){if(!arguments.length)return[b,c];b=i[0];c=i[1];g=d.length/(c-b);return a};a.range=function(i){if(!arguments.length)return d;d=i;g=d.length/(c-b);e=d.length-1;return a};return a};d3.svg={};d3.svg.arc=function(){function a(d,i){var f=b.call(this,d,i),h=c.call(this,d,i),k=g.call(this,d,i)+O,j=e.call(this,d,i)+O,o=j-k,p=o=Za?f?"M0,"+h+ +"A"+h+","+h+" 0 1,1 0,"+-h+"A"+h+","+h+" 0 1,1 0,"+h+"M0,"+f+"A"+f+","+f+" 0 1,1 0,"+-f+"A"+f+","+f+" 0 1,1 0,"+f+"Z":"M0,"+h+"A"+h+","+h+" 0 1,1 0,"+-h+"A"+h+","+h+" 0 1,1 0,"+h+"Z":f?"M"+h*l+","+h*k+"A"+h+","+h+" 0 "+p+",1 "+h*n+","+h*j+"L"+f*n+","+f*j+"A"+f+","+f+" 0 "+p+",0 "+f*l+","+f*k+"Z":"M"+h*l+","+h*k+"A"+h+","+h+" 0 "+p+",1 "+h*n+","+h*j+"L0,0Z"}var b=Ia,c=Ja,g=ia,e=ja;a.innerRadius=function(d){if(!arguments.length)return b;b=x(d);return a};a.outerRadius=function(d){if(!arguments.length)return c; +c=x(d);return a};a.startAngle=function(d){if(!arguments.length)return g;g=x(d);return a};a.endAngle=function(d){if(!arguments.length)return e;e=x(d);return a};return a};var O=-Math.PI/2,Za=2*Math.PI-1.0E-6;d3.svg.line=function(){function a(i){return i.length<1?null:"M"+e(Z(this,i,b,c),d)}var b=ka,c=la,g="linear",e=P[g],d=0.7;a.x=function(i){if(!arguments.length)return b;b=i;return a};a.y=function(i){if(!arguments.length)return c;c=i;return a};a.interpolate=function(i){if(!arguments.length)return g; +e=P[g=i];return a};a.tension=function(i){if(!arguments.length)return d;d=i;return a};return a};var P={linear:I,basis:function(a){if(a.length<3)return I(a);var b=[],c=1,g=a.length,e=a[0],d=e[0],i=e[1],f=[d,d,d,(e=a[1])[0]],h=[i,i,i,e[1]];b.push(d,",",i);for(L(b,f,h);++c= v[d.x] - && mint <= v[d.y] - && maxt >= v[d.y]; - }); - svg.selectAll("circle") .attr("fill", function(d) { - return d.y.on ? color(d.y.species) : "#ccc"; + return mins <= d.y[v.x] && maxs >= d.y[v.x] + && mint <= d.y[v.y] && maxt >= d.y[v.y] + ? (count++, color(d.y.species)) + : "#ccc"; }); } diff --git a/examples/stream/stream.html b/examples/stream/stream.html index 4850cac7..4252d887 100644 --- a/examples/stream/stream.html +++ b/examples/stream/stream.html @@ -37,17 +37,16 @@ var vis = d3.select("body") .attr("width", w) .attr("height", h); -vis.selectAll("path.area") +vis.selectAll("path") .data(data0) .enter().append("svg:path") - .attr("class", "area") .attr("fill", function() { return color(Math.random()); }) .attr("d", area); window.addEventListener("keypress", transition, false); function transition() { - d3.selectAll("path.area") + d3.selectAll("path") .data(function() { var d = data1; data1 = data0; diff --git a/examples/zoom-pan/zoom-pan.html b/examples/zoom-pan/zoom-pan.html new file mode 100644 index 00000000..c6362c16 --- /dev/null +++ b/examples/zoom-pan/zoom-pan.html @@ -0,0 +1,114 @@ + + + + + Zoom + Pan + + + + + + + + diff --git a/src/behavior/behavior.js b/src/behavior/behavior.js new file mode 100644 index 00000000..39633b10 --- /dev/null +++ b/src/behavior/behavior.js @@ -0,0 +1 @@ +d3.behavior = {}; diff --git a/src/behavior/zoom.js b/src/behavior/zoom.js new file mode 100644 index 00000000..16eb5c57 --- /dev/null +++ b/src/behavior/zoom.js @@ -0,0 +1,130 @@ +// TODO unbind zoom behavior? +// TODO unbind listener? +d3.behavior.zoom = function() { + + // https://bugs.webkit.org/show_bug.cgi?id=40441 + var bug40441 = /WebKit\/533/.test(navigator.userAgent) ? -1 : 0, + bug40441Last = 0, + x = 0, + y = 0, + z = 0, + listeners = [], + pan, + zoom; + + function zoom() { + var container = this + .on("mousedown", mousedown) + .on("mousewheel", mousewheel) + .on("DOMMouseScroll", mousewheel) + .on("dblclick", mousewheel); + + d3.select(window) + .on("mousemove", mousemove) + .on("mouseup", mouseup); + } + + function mousedown(d, i) { + pan = { + x0: x - d3.event.clientX, + y0: y - d3.event.clientY, + target: this, + data: d, + index: i + }; + d3.event.preventDefault(); + window.focus(); // TODO focusableParent + } + + function mousemove() { + zoom = null; + if (pan) { + x = d3.event.clientX + pan.x0; + y = d3.event.clientY + pan.y0; + dispatch.call(pan.target, pan.data, pan.index); + } + } + + function mouseup() { + if (pan) { + mousemove(); + pan = null; + } + } + + function mousewheel(d, i) { + var e = d3.event; + + // initialize the mouse location for zooming (to avoid drift) + if (!zoom) { + var p = d3.svg.mouse(this.nearestViewportElement || this); + zoom = { + x0: x, + y0: y, + z0: z, + x1: x - p[0], + y1: y - p[1] + }; + } + + // adjust zoom level + if (e.type == "dblclick") { + z = e.shiftKey ? Math.ceil(z - 1) : Math.floor(z + 1); + } else { + var delta = (e.wheelDelta / 120 || -e.detail) * .1; + + /* Detect fast & large wheel events on WebKit. */ + if (bug40441 < 0) { + var now = Date.now(), since = now - bug40441Last; + if ((since > 9) && (Math.abs(e.wheelDelta) / since >= 50)) bug40441 = 1; + bug40441Last = now; + } + if (bug40441 == 1) delta *= .03; + + z += delta; + } + + // adjust x and y to center around mouse location + var k = Math.pow(2, z - zoom.z0) - 1; + x = zoom.x0 + zoom.x1 * k; + y = zoom.y0 + zoom.y1 * k; + + // dispatch redraw + dispatch.call(this, d, i); + } + + function dispatch(d, i) { + var o = d3.event, // Events can be reentrant (e.g., focus). + k = Math.pow(2, z); + + d3.event = { + scale: k, + translate: [x, y], + transform: function(sx, sy) { + if (sx) transform(sx, x); + if (sy) transform(sy, y); + } + }; + + function transform(scale, o) { + var domain = scale.__domain || (scale.__domain = scale.domain()); + range = scale.range().map(function(v) { return (v - o) / k; }); + scale.domain(domain).domain(range.map(scale.invert)); + } + + try { + for (var j = 0, m = listeners.length; j < m; j++) { + listeners[j].call(this, d, i); + } + } finally { + d3.event = o; + } + } + + zoom.on = function(type, listener) { + if (type == "zoom") listeners.push(listener); + return zoom; + }; + + return zoom; +}; diff --git a/src/core/core.js b/src/core/core.js index e6e1b3b6..5ad0cd39 100644 --- a/src/core/core.js +++ b/src/core/core.js @@ -1 +1 @@ -d3 = {version: "0.30.3"}; // semver +d3 = {version: "0.30.4"}; // semver diff --git a/src/core/format.js b/src/core/format.js index 0675a768..645375ce 100644 --- a/src/core/format.js +++ b/src/core/format.js @@ -1,7 +1,8 @@ -// TODO align, sign, type +// TODO align, type d3.format = function(specifier) { var match = d3_format_re.exec(specifier), fill = match[1] || " ", + sign = d3_format_signs[match[3]] || d3_format_signs["-"], zfill = match[5], width = +match[6], comma = match[7], @@ -11,9 +12,11 @@ d3.format = function(specifier) { if (zfill) fill = "0"; // TODO align = "="; if (type == "d") precision = "0"; return function(value) { - if ((type == "d") && (value % 1)) return ""; - if (precision) value = (+value).toFixed(precision); - else value += ""; + var number = +value, + negative = (number < 0) && (number = -number); + if ((type == "d") && (number % 1)) return ""; + if (precision) value = number.toFixed(precision); + else value = "" + number; if (comma) { var i = value.lastIndexOf("."), f = i >= 0 ? value.substring(i) : (i = value.length, ""), @@ -21,11 +24,17 @@ d3.format = function(specifier) { while (i > 0) t.push(value.substring(i -= 3, i + 3)); value = t.reverse().join(",") + f; } - var n = value.length; - if (n < width) value = new Array(width - n + 1).join(fill) + value; + var length = (value = sign(negative, value)).length; + if (length < width) value = new Array(width - length + 1).join(fill) + value; return value; }; }; // [[fill]align][sign][#][0][width][,][.precision][type] var d3_format_re = /(?:([^{])?([<>=^]))?([+\- ])?(#)?(0)?([0-9]+)?(,)?(\.[0-9]+)?([a-zA-Z%])?/; + +var d3_format_signs = { + "+": function(negative, value) { return (negative ? "\u2212" : "+") + value; }, + " ": function(negative, value) { return (negative ? "\u2212" : " ") + value; }, + "-": function(negative, value) { return negative ? "\u2212" + value : value; } +}; diff --git a/src/scale/pow.js b/src/scale/pow.js index 6a338bfb..56ae2898 100644 --- a/src/scale/pow.js +++ b/src/scale/pow.js @@ -5,11 +5,11 @@ d3.scale.pow = function() { b = 1 / p; function powp(x) { - return Math.pow(x, p); + return x < 0 ? -Math.pow(-x, p) : Math.pow(x, p); } function powb(x) { - return Math.pow(x, b); + return x < 0 ? -Math.pow(-x, b) : Math.pow(x, b); } function scale(x) {