From d16d06ef36fca98bab6d4a8268e1422596b5d794 Mon Sep 17 00:00:00 2001 From: Michael Bostock Date: Sun, 24 Oct 2010 17:05:59 -0700 Subject: [PATCH] First pass at some shape utilities. --- Makefile | 3 + d3.js | 117 ++++++++++++++++++++++++++++++++++++++ d3.min.js | 63 ++++++++++---------- examples/area/area.html | 35 ++++-------- examples/donut/donut.html | 53 ++++++----------- examples/line/line.html | 40 ++++++------- src/arc.js | 56 ++++++++++++++++++ src/area.js | 36 ++++++++++++ src/line.js | 25 ++++++++ 9 files changed, 316 insertions(+), 112 deletions(-) create mode 100644 src/arc.js create mode 100644 src/area.js create mode 100644 src/line.js diff --git a/Makefile b/Makefile index 821f1546..4e4f8c4c 100644 --- a/Makefile +++ b/Makefile @@ -32,6 +32,9 @@ SRC_FILES = \ src/transition.js \ src/timer.js \ src/tween.js \ + src/arc.js \ + src/line.js \ + src/area.js \ src/end.js all: d3.js d3.min.js diff --git a/d3.js b/d3.js index 2179c6a7..df82114c 100644 --- a/d3.js +++ b/d3.js @@ -1457,4 +1457,121 @@ function d3_tween(b) { ? function(d, i, a) { return d3.interpolate(a, b.call(this, d, i)); } : function(d, i, a) { return d3.interpolate(a, b); }; } +d3.arc = function() { + var innerRadius = function(d) { return d.innerRadius; }, + outerRadius = function(d) { return d.outerRadius; }, + startAngle = function(d) { return d.startAngle; }, + endAngle = function(d) { return d.endAngle; }; + + function arc(d) { + var r0 = innerRadius(d), + r1 = outerRadius(d), + a0 = startAngle(d) - Math.PI / 2, + a1 = endAngle(d) - Math.PI / 2, + da = a1 - a0, + c0 = Math.cos(a0), + s0 = Math.sin(a0), + c1 = Math.cos(a1), + s1 = Math.sin(a1); + return "M" + r1 * c0 + "," + r1 * s0 + + "A" + r1 + "," + r1 + " 0 " + + ((da < Math.PI) ? "0" : "1") + ",1 " + + r1 * c1 + "," + r1 * s1 + + "L" + r0 * c1 + "," + r0 * s1 + + "A" + r0 + "," + r0 + " 0 " + + ((da < Math.PI) ? "0" : "1") + ",0 " + + r0 * c0 + "," + r0 * s0 + "Z"; + } + + arc.innerRadius = function(value) { + innerRadius = typeof value == "function" + ? value + : function() { return value; }; + return arc; + }; + + arc.outerRadius = function(value) { + outerRadius = typeof value == "function" + ? value + : function() { return value; }; + return arc; + }; + + arc.startAngle = function(value) { + startAngle = typeof value == "function" + ? value + : function() { return value; }; + return arc; + }; + + arc.endAngle = function(value) { + endAngle = typeof value == "function" + ? value + : function() { return value; }; + return arc; + }; + + return arc; +}; +d3.line = function() { + var x = function(d, i) { return d.x; }, + y = function(d, i) { return d.y; }; + + function line(d) { + var a = [], + i = 0, + p = d[0]; + a.push("M", x.call(this, p, i), ",", y.call(this, p, i)); + while (p = d[++i]) a.push("L", x.call(this, p, i), ",", y.call(this, p, i)); + return a.join(""); + } + + line.x = function(value) { + x = value; + return line; + }; + + line.y = function(value) { + y = value; + return line; + }; + + return line; +}; +d3.area = function() { + var x = function(d, i) { return d.x; }, + y0 = 0, + y1 = function(d, i) { return d.y1; }; + + // TODO interpolators + // TODO variable y0 + // TODO horizontal / vertical orientation + + function area(d) { + var a = [], + i = 0, + p = d[0]; + a.push("M", x.call(this, p, i), "," + y0 + "V", y1.call(this, p, i)); + while (p = d[++i]) a.push("L", x.call(this, p, i), ",", y1.call(this, p, i)); + a.push("V" + y0 + "Z"); + return a.join(""); + } + + area.x = function(value) { + x = value; + return area; + }; + + area.y0 = function(value) { + y0 = value; + return area; + }; + + area.y1 = function(value) { + y1 = value; + return area; + }; + + return area; +}; })(this); diff --git a/d3.min.js b/d3.min.js index ebc9ca12..0dddf02f 100644 --- a/d3.min.js +++ b/d3.min.js @@ -1,37 +1,40 @@ if(!Date.now)Date.now=function(){return+new Date};if(!Object.create)Object.create=function(I){function E(){}E.prototype=I;return new E}; -(function(I){function E(){var a={},b=[];a.add=function(e){for(var f=0;f360)h-=360;else if(h<0)h+=360;if(h<60)return d+(c-d)*h/60;if(h<180)return c;if(h<240)return d+(c-d)*(240-h)/60;return d}var d,c;a%=360;if(a<0)a+=360;b=b<0?0:b>1?1:b;e=e<0?0:e>1?1:e;c=e<=0.5?e*(1+b):e+b-e*b;d=2*e-c;return{r:Math.round(f(a+120)*255),g:Math.round(f(a)*255),b:Math.round(f(a-120)*255)}}function L(a){var b=parseFloat(a);return a.charAt(a.length-1)=="%"?Math.round(b*2.55):b}function z(a){function b(d){for(var c=[],h,i,g,n,p=0,j=a.length;p=1)q=1;else{k=false;if(q<0)return;if(!h[o]){h[o]=1;c.start.dispatch.apply(this, -arguments)}}var v=p(q),m;for(m in d)d[m].call(this,v,o);if(q==1){h[o]=2;c.end.dispatch.apply(this,arguments)}}});return k}var f={},d={},c=l.dispatch("start","end"),h=[],i=[],g=[],n,p=l.ease("cubic-in-out");f.delay=function(j){var k=Infinity,o=-1;if(typeof j=="function")a.each(function(){var q=i[++o]=+j.apply(this,arguments);if(qn)n=o})}else{n=+j;a.each(function(){g[++k]=n})}return f};f.ease=function(j){p=typeof j=="string"?l.ease(j):j;return f};f.attrTween=function(j,k){function o(r,t){s[++A]=k.call(this,r,t,this.getAttribute(j))}function q(r,t){s[++A]=k.call(this,r,t,this.getAttributeNS(j.space,j.local))}function v(r,t){this.setAttribute(j,s[t](r))}function m(r,t){this.setAttributeNS(j.space,j.local,s[t](r))}var s=[],A=-1;j=l.ns.qualify(j);a.each(j.local?q:o);d["attr."+j]=j.local?m:v;return f};f.attr=function(j, -k){return f.attrTween(j,T(k))};f.styleTween=function(j,k,o){var q=[],v=-1;a.each(function(m,s){q[++v]=k.call(this,m,s,window.getComputedStyle(this,null).getPropertyValue(j))});d["style."+j]=function(m,s){this.style.setProperty(j,q[s](m),o)};return f};f.style=function(j,k,o){return f.styleTween(j,T(k),o)};f.select=function(j){var k;j=M(a.select(j),b).ease(p);k=-1;j.delay(function(){return i[++k]});k=-1;j.duration(function(){return g[++k]});return j};f.selectAll=function(j){var k;j=M(a.selectAll(j), -b).ease(p);k=-1;j.delay(function(o,q){return i[q?k:++k]});k=-1;j.duration(function(o,q){return g[q?k:++k]});return j};f.each=function(j,k){c[j].add(k);return f};return f.delay(0).duration(250)}function aa(a,b){for(var e=Date.now(),f=false,d=e+b,c=D;c;){if(c.callback==a){c.then=e;c.delay=b;f=true}else{var h=c.then+c.delay;if(he.delay)e.flush=e.callback(a);e=e.next}a=null;for(b=D;b;)b=b.flush?a?a.next=b.next:D=b.next:(a=b).next;a||(G=clearInterval(G))}function T(a){return typeof a=="function"?function(b,e,f){return l.interpolate(f,a.call(this,b,e))}:function(b,e,f){return l.interpolate(f,a)}}var l=I.d3={};l.version="0.1.0";l.range=function(a,b,e){if(arguments.length==1){b=a;a=0}if(e==null)e=1;if((b-a)/e==Infinity)throw Error("infinite range");var f=[],d=-1,c;if(e<0)for(;(c=a+e*++d)> -b;)f.push(c);else for(;(c=a+e*++d)=0?a.substring(0,b):a;b=b>=0?a.substring(b+1):"in";return ga[b](fa[e].apply(null,Array.prototype.slice.call(arguments,1)))};l.event=null;l.interpolate=function(a,b){if(typeof b=="number")return l.interpolateNumber(+a,b);if(typeof b=="string")return b in B||/^(#|rgb\(|hsl\()/.test(b)?l.interpolateRgb(String(a),b):l.interpolateString(String(a),b);if(b instanceof Array)return l.interpolateArray(a,b);return l.interpolateObject(a, -b)};l.interpolateNumber=function(a,b){b-=a;return function(e){return a+b*e}};l.interpolateString=function(a,b){var e,f,d=0,c=[],h=[],i,g;for(f=0;e=O.exec(b);++f){e.index&&c.push(b.substring(d,e.index));h.push({i:c.length,x:e[0]});c.push(null);d=O.lastIndex}d360)g-=360;else if(g<0)g+=360;if(g<60)return d+(b-d)*g/60;if(g<180)return b;if(g<240)return d+(b-d)*(240-g)/60;return d}var d,b;a%=360;if(a<0)a+=360;c=c<0?0:c>1?1:c;e=e<0?0:e>1?1:e;b=e<=0.5?e*(1+c):e+c-e*c;d=2*e-b;return{r:Math.round(f(a+120)*255),g:Math.round(f(a)*255),b:Math.round(f(a-120)*255)}}function L(a){var c=parseFloat(a);return a.charAt(a.length-1)=="%"?Math.round(c*2.55):c}function z(a){function c(d){for(var b=[],g,i,h,n,o=0,j=a.length;o=1)q=1;else{k=false;if(q<0)return;if(!g[p]){g[p]=1;b.start.dispatch.apply(this, +arguments)}}var v=o(q),m;for(m in d)d[m].call(this,v,p);if(q==1){g[p]=2;b.end.dispatch.apply(this,arguments)}}});return k}var f={},d={},b=l.dispatch("start","end"),g=[],i=[],h=[],n,o=l.ease("cubic-in-out");f.delay=function(j){var k=Infinity,p=-1;if(typeof j=="function")a.each(function(){var q=i[++p]=+j.apply(this,arguments);if(qn)n=p})}else{n=+j;a.each(function(){h[++k]=n})}return f};f.ease=function(j){o=typeof j=="string"?l.ease(j):j;return f};f.attrTween=function(j,k){function p(r,t){s[++A]=k.call(this,r,t,this.getAttribute(j))}function q(r,t){s[++A]=k.call(this,r,t,this.getAttributeNS(j.space,j.local))}function v(r,t){this.setAttribute(j,s[t](r))}function m(r,t){this.setAttributeNS(j.space,j.local,s[t](r))}var s=[],A=-1;j=l.ns.qualify(j);a.each(j.local?q:p);d["attr."+j]=j.local?m:v;return f};f.attr=function(j, +k){return f.attrTween(j,T(k))};f.styleTween=function(j,k,p){var q=[],v=-1;a.each(function(m,s){q[++v]=k.call(this,m,s,window.getComputedStyle(this,null).getPropertyValue(j))});d["style."+j]=function(m,s){this.style.setProperty(j,q[s](m),p)};return f};f.style=function(j,k,p){return f.styleTween(j,T(k),p)};f.select=function(j){var k;j=M(a.select(j),c).ease(o);k=-1;j.delay(function(){return i[++k]});k=-1;j.duration(function(){return h[++k]});return j};f.selectAll=function(j){var k;j=M(a.selectAll(j), +c).ease(o);k=-1;j.delay(function(p,q){return i[q?k:++k]});k=-1;j.duration(function(p,q){return h[q?k:++k]});return j};f.each=function(j,k){b[j].add(k);return f};return f.delay(0).duration(250)}function aa(a,c){for(var e=Date.now(),f=false,d=e+c,b=D;b;){if(b.callback==a){b.then=e;b.delay=c;f=true}else{var g=b.then+b.delay;if(ge.delay)e.flush=e.callback(a);e=e.next}a=null;for(c=D;c;)c=c.flush?a?a.next=c.next:D=c.next:(a=c).next;a||(G=clearInterval(G))}function T(a){return typeof a=="function"?function(c,e,f){return l.interpolate(f,a.call(this,c,e))}:function(c,e,f){return l.interpolate(f,a)}}var l=I.d3={};l.version="0.1.0";l.range=function(a,c,e){if(arguments.length==1){c=a;a=0}if(e==null)e=1;if((c-a)/e==Infinity)throw Error("infinite range");var f=[],d=-1,b;if(e<0)for(;(b=a+e*++d)> +c;)f.push(b);else for(;(b=a+e*++d)=0?a.substring(0,c):a;c=c>=0?a.substring(c+1):"in";return ga[c](fa[e].apply(null,Array.prototype.slice.call(arguments,1)))};l.event=null;l.interpolate=function(a,c){if(typeof c=="number")return l.interpolateNumber(+a,c);if(typeof c=="string")return c in B||/^(#|rgb\(|hsl\()/.test(c)?l.interpolateRgb(String(a),c):l.interpolateString(String(a),c);if(c instanceof Array)return l.interpolateArray(a,c);return l.interpolateObject(a, +c)};l.interpolateNumber=function(a,c){c-=a;return function(e){return a+c*e}};l.interpolateString=function(a,c){var e,f,d=0,b=[],g=[],i,h;for(f=0;e=O.exec(c);++f){e.index&&b.push(c.substring(d,e.index));g.push({i:b.length,x:e[0]});b.push(null);d=O.lastIndex}d diff --git a/examples/donut/donut.html b/examples/donut/donut.html index 0c531072..eb6b730d 100644 --- a/examples/donut/donut.html +++ b/examples/donut/donut.html @@ -15,23 +15,24 @@ body { var w = 400, h = 400, - r = Math.min(w, h) / 2, + r1 = Math.min(w, h) / 2, + r0 = r1 * .6, n = 10, - data = normalize(d3.range(n).map(Math.random)), - color = d3.category20(); + data = arcs(d3.range(n).map(Math.random)), + color = d3.category20(), + arc = d3.arc(); var vis = d3.select("body") .append("svg:svg") .attr("width", w) .attr("height", h); -var arcs = vis.selectAll("g.arc") +vis.selectAll("g.arc") .data(data) .enter("svg:g") .attr("class", "arc") - .attr("transform", "translate(" + r + "," + r + ")"); - -arcs.append("svg:path") + .attr("transform", "translate(" + r1 + "," + r1 + ")") + .append("svg:path") .attr("fill", function(d, i) { return color(i); }) .attr("d", arc); @@ -39,7 +40,7 @@ window.addEventListener("keypress", update, false); function update() { var prev = data, - next = data = normalize(d3.range(n).map(Math.random)), + next = data = arcs(d3.range(n).map(Math.random)), i = 0; for (; i < n; ++i) prev[i].next = next[i]; d3.selectAll("g.arc > path") @@ -52,8 +53,8 @@ function transitionSplit(d, i) { d3.select(this) .transition() .attrTween("d", tweenArc({ - innerRadius: i & 1 ? r * .6 : r * .8, - outerRadius: i & 1 ? r * .8 : r + innerRadius: i & 1 ? r0 : (r0 + r1) / 2, + outerRadius: i & 1 ? (r0 + r1) / 2 : r1 })) .each("end", transitionRotate); } @@ -102,39 +103,19 @@ function tweenArc(b) { }; } -function normalize(array) { - var k = (2 * Math.PI) / array.reduce(function(p, d) { return p + d; }, 0), +function arcs(values) { + var k = (2 * Math.PI) / values.reduce(function(p, d) { return p + d; }, 0), a = 0; - return array.map(function(d, i) { + return values.map(function(d, i) { return { - innerRadius: r * .6, - outerRadius: r, startAngle: a, - endAngle: a += d * k + endAngle: a += d * k, + innerRadius: r0, + outerRadius: r1 }; }); } -function arc(d) { - var r0 = d.innerRadius, - r1 = d.outerRadius, - a0 = d.startAngle - Math.PI / 2, - a1 = d.endAngle - Math.PI / 2, - da = a1 - a0, - c0 = Math.cos(a0), - s0 = Math.sin(a0), - c1 = Math.cos(a1), - s1 = Math.sin(a1); - return "M" + r1 * c0 + "," + r1 * s0 - + "A" + r1 + "," + r1 + " 0 " - + ((da < Math.PI) ? "0" : "1") + ",1 " - + r1 * c1 + "," + r1 * s1 - + "L" + r0 * c1 + "," + r0 * s1 - + "A" + r0 + "," + r0 + " 0 " - + ((da < Math.PI) ? "0" : "1") + ",0 " - + r0 * c0 + "," + r0 * s0 + "Z"; -} - diff --git a/examples/line/line.html b/examples/line/line.html index c6f3cdb4..3a6653bf 100644 --- a/examples/line/line.html +++ b/examples/line/line.html @@ -38,9 +38,12 @@ var data = d3.range(20).map(function(i) { var w = 450, h = 275, - p = 20; + p = 20, + x = d3.linear().domain([0, 1]).range([0, w]), + y = d3.linear().domain([0, 1]).range([h, 0]); var vis = d3.select("body") + .data([data]) .append("svg:svg") .attr("width", w + p * 2) .attr("height", h + p * 2) @@ -48,58 +51,51 @@ var vis = d3.select("body") .attr("transform", "translate(" + p + "," + p + ")"); var rules = vis.selectAll("g.rule") - .data(d3.range(11)) + .data(x.ticks(10)) .enter("svg:g") .attr("class", "rule"); rules.append("svg:line") - .attr("x1", function(d) { return d / 10 * w; }) - .attr("x2", function(d) { return d / 10 * w; }) + .attr("x1", x) + .attr("x2", x) .attr("y1", 0) .attr("y2", h - 1); rules.append("svg:line") .attr("class", function(d) { return d ? null : "axis"; }) - .attr("y1", function(d) { return (1 - d / 10) * h; }) - .attr("y2", function(d) { return (1 - d / 10) * h; }) + .attr("y1", y) + .attr("y2", y) .attr("x1", 0) .attr("x2", w + 1); rules.append("svg:text") - .attr("x", function(d) { return d / 10 * w; }) + .attr("x", x) .attr("y", h + 3) .attr("dy", ".71em") .attr("text-anchor", "middle") - .text(function(d) { return (d / 10).toFixed(1); }); + .text(x.tickFormat(10)); rules.append("svg:text") - .attr("y", function(d) { return (1 - d / 10) * h; }) + .attr("y", y) .attr("x", -3) .attr("dy", ".35em") .attr("text-anchor", "end") - .text(function(d) { return (d / 10).toFixed(1); }); + .text(y.tickFormat(10)); vis.append("svg:path") .attr("class", "line") - .attr("d", line(data)); + .attr("d", d3.line() + .x(function(d) { return x(d.x); }) + .y(function(d) { return y(d.y); })); vis.selectAll("circle.line") .data(data) .enter("svg:circle") .attr("class", "line") - .attr("cx", function(d) { return d.x * w; }) - .attr("cy", function(d) { return (1 - d.y) * h; }) + .attr("cx", function(d) { return x(d.x); }) + .attr("cy", function(d) { return y(d.y); }) .attr("r", 3.5); -function line(points) { - var d = [], - i = 1, - p = points[0]; - d.push("M", p.x * w, ",", (1 - p.y) * h); - while (p = points[i++]) d.push("L", p.x * w, ",", (1 - p.y) * h); - return d.join(""); -} - diff --git a/src/arc.js b/src/arc.js new file mode 100644 index 00000000..9e14d5ce --- /dev/null +++ b/src/arc.js @@ -0,0 +1,56 @@ +d3.arc = function() { + var innerRadius = function(d) { return d.innerRadius; }, + outerRadius = function(d) { return d.outerRadius; }, + startAngle = function(d) { return d.startAngle; }, + endAngle = function(d) { return d.endAngle; }; + + function arc(d) { + var r0 = innerRadius(d), + r1 = outerRadius(d), + a0 = startAngle(d) - Math.PI / 2, + a1 = endAngle(d) - Math.PI / 2, + da = a1 - a0, + c0 = Math.cos(a0), + s0 = Math.sin(a0), + c1 = Math.cos(a1), + s1 = Math.sin(a1); + return "M" + r1 * c0 + "," + r1 * s0 + + "A" + r1 + "," + r1 + " 0 " + + ((da < Math.PI) ? "0" : "1") + ",1 " + + r1 * c1 + "," + r1 * s1 + + "L" + r0 * c1 + "," + r0 * s1 + + "A" + r0 + "," + r0 + " 0 " + + ((da < Math.PI) ? "0" : "1") + ",0 " + + r0 * c0 + "," + r0 * s0 + "Z"; + } + + arc.innerRadius = function(value) { + innerRadius = typeof value == "function" + ? value + : function() { return value; }; + return arc; + }; + + arc.outerRadius = function(value) { + outerRadius = typeof value == "function" + ? value + : function() { return value; }; + return arc; + }; + + arc.startAngle = function(value) { + startAngle = typeof value == "function" + ? value + : function() { return value; }; + return arc; + }; + + arc.endAngle = function(value) { + endAngle = typeof value == "function" + ? value + : function() { return value; }; + return arc; + }; + + return arc; +}; diff --git a/src/area.js b/src/area.js new file mode 100644 index 00000000..5ba7753d --- /dev/null +++ b/src/area.js @@ -0,0 +1,36 @@ +d3.area = function() { + var x = function(d, i) { return d.x; }, + y0 = 0, + y1 = function(d, i) { return d.y1; }; + + // TODO interpolators + // TODO variable y0 + // TODO horizontal / vertical orientation + + function area(d) { + var a = [], + i = 0, + p = d[0]; + a.push("M", x.call(this, p, i), "," + y0 + "V", y1.call(this, p, i)); + while (p = d[++i]) a.push("L", x.call(this, p, i), ",", y1.call(this, p, i)); + a.push("V" + y0 + "Z"); + return a.join(""); + } + + area.x = function(value) { + x = value; + return area; + }; + + area.y0 = function(value) { + y0 = value; + return area; + }; + + area.y1 = function(value) { + y1 = value; + return area; + }; + + return area; +}; diff --git a/src/line.js b/src/line.js new file mode 100644 index 00000000..c24a8a1d --- /dev/null +++ b/src/line.js @@ -0,0 +1,25 @@ +d3.line = function() { + var x = function(d, i) { return d.x; }, + y = function(d, i) { return d.y; }; + + function line(d) { + var a = [], + i = 0, + p = d[0]; + a.push("M", x.call(this, p, i), ",", y.call(this, p, i)); + while (p = d[++i]) a.push("L", x.call(this, p, i), ",", y.call(this, p, i)); + return a.join(""); + } + + line.x = function(value) { + x = value; + return line; + }; + + line.y = function(value) { + y = value; + return line; + }; + + return line; +};