modules/enterprise/gui/rest-war/src/main/webapp/index.html | 3 modules/enterprise/gui/rest-war/src/main/webapp/js/d3.csv.js | 92 modules/enterprise/gui/rest-war/src/main/webapp/js/d3.geo.js | 938 ++ modules/enterprise/gui/rest-war/src/main/webapp/js/d3.geom.js | 835 ++ modules/enterprise/gui/rest-war/src/main/webapp/js/d3.js | 3135 ++++++---- modules/enterprise/gui/rest-war/src/main/webapp/js/d3.layout.js | 454 - modules/enterprise/gui/rest-war/src/main/webapp/js/d3.time.js | 61 modules/enterprise/gui/rest-war/src/main/webapp/raw_graph.html | 179 8 files changed, 4419 insertions(+), 1278 deletions(-)
New commits: commit 3fa0177b1a7c943bd83291f35f4027c128f26984 Author: Heiko W. Rupp hwr@redhat.com Date: Fri Jan 6 14:16:40 2012 +0100
Add an example of graphing raw data with D3.js as dots or lines. Use D3 helpers for max/min, draw a line for the avg.
diff --git a/modules/enterprise/gui/rest-war/src/main/webapp/index.html b/modules/enterprise/gui/rest-war/src/main/webapp/index.html index 9e96d76..7162c24 100644 --- a/modules/enterprise/gui/rest-war/src/main/webapp/index.html +++ b/modules/enterprise/gui/rest-war/src/main/webapp/index.html @@ -1,7 +1,7 @@ <html> <body> <h2>RHQ Rest API</h2> -<b>Please note: the API is not stable at this point. Do not rely on it.</b><br/> +<strong>Please note: the API is not stable at this point. Do not rely on it.</strong><br/>
Join us in making it great by providing feedback and contributions of code and examples that use the api. @@ -28,6 +28,7 @@ an empty database (meaning resource ids and schedule ids starting at 10001). <li><a href="whisker.html">Whisker chart 1</a></li> <li><a href="whisker2.html">Multiple Whisker charts</a></li> <li><a href="tree.html">Resource tree</a></li> + <li><a href="raw_graph.html">7 days of raw metrics as dot- or line- chart</a></li>
</ul> </body> diff --git a/modules/enterprise/gui/rest-war/src/main/webapp/raw_graph.html b/modules/enterprise/gui/rest-war/src/main/webapp/raw_graph.html new file mode 100644 index 0000000..598a0b8 --- /dev/null +++ b/modules/enterprise/gui/rest-war/src/main/webapp/raw_graph.html @@ -0,0 +1,179 @@ +<!DOCTYPE html> +<html> + <head> + <title>Raw mertic last 7 days</title> + <script type="text/javascript" src="js/d3.js"></script> + <script type="text/javascript" src="js/d3.layout.js"></script> + <script type="text/javascript" src="js/d3.time.js"></script> + <script type="text/javascript" src="js/jquery.js"></script> + <style type="text/css"> + +svg { + width: 960px; + height: 600px; + border: solid 1px #ccc; + font: 10px sans-serif; + shape-rendering: crispEdges; +} +.axis text +{ + fill: #333; + font-family: sans-serif; + font-size: 10px; +} +.axis .domain +{ + opacity: 0; +} +.tick +{ + stroke: #ccc; + stroke-dasharray: 1 5; +} + +path { + stroke: steelblue; + stroke-width: 2; + fill: none; +} + +line { + stroke: black; +} + + </style> + </head> + <body> + <h1 id="resId">Resource</h1> + <h2 id="header">Schedule</h2> +<button id="dotsButton" onClick="transitionDots() ">Dots</button> +<button id="lineButton" onClick="transitionLine()">Lines</button> + + <div id="one"></div> + <script type="text/javascript"> + +// Width and height of the chart to print +var w = 960, + h = 600; + +// Get the platforms and then the first one +$.getJSON('/rest/1/resource/platforms.json', function (json) { + var res = json[0]; + var rid = res.resourceId; + $("#resId").text("Resource " + rid + " (" + res.resourceName + ")"); + + // Now get the metrical schedules of the platform + $.getJSON("/rest/1/resource/"+rid+"/schedules.json?type=metric", function (json2) { + + // Take the first and render it + var schId = json2[0].scheduleId; + $("#header").text("Schedule " + schId + " (" + json2[0].scheduleName + "), base unit: " + json2[0].unit); + + d3.json( + '/rest/1/metric/data/' + schId + '/raw.json?duration=604800', + function (jsondata) { + + var w = 960, + h = 600; + // compute min/max values for x and y + var minVal = d3.min(jsondata,function(d) { return d.value;}); + var maxVal = d3.max(jsondata,function(d) { return d.value;}); + var minTsD = d3.min(jsondata,function(d) { return d.timeStamp;}); + var maxTsD = d3.max(jsondata,function(d) { return d.timeStamp;}); + + var avg = d3.sum(jsondata,function(d) { return d.value;}) / jsondata.length; + + + WIDTH = w; + HEIGHT = h; + MARGINS = {top: 10, right: 10, bottom: 20, left: 10}; + + var xRange = d3.time.scale().range ([MARGINS.left, WIDTH - MARGINS.right]).domain([new Date(minTsD),new Date(maxTsD)]); + var yRange = d3.scale.linear().range ([HEIGHT - MARGINS.top, MARGINS.bottom]).domain([minVal, maxVal]); + + // X axis range goes from lowest to highest timestamp + var x = d3.time.scale().domain([new Date(minTsD),new Date(maxTsD)]).range([50, w-10]); // 50-> make room for label + var y = yRange; + // Y axis range goes from lowest to highest value + // var y = d3.scale.linear().domain([minVal, maxVal]).rangeRound([0, h]); + format = d3.time.format("%H:%M"); + + + xAxis = d3.svg.axis() // generate an axis + .scale(x) // set the range of the axis + .tickSize(580) // height of the ticks + .tickSubdivide(true); // display ticks between text labels + yAxis = d3.svg.axis() // generate an axis + .scale(yRange) // set the range of the axis + .tickSize(3) // width of the ticks + .orient("right") // have the text labels on the right hand side + .tickFormat(d3.format("0e")) + .tickSubdivide(true); // display ticks between text labels + + // create the SVG + var svg = d3.select("body").append("svg:svg") + .attr("width", w) + .attr("height", h); + + // Create axes + svg.append("svg:g") + .attr("class","x axis") + .call(xAxis); + + svg.append("svg:g") + .attr("class","y axis") + .attr("transform", "translate(" + (MARGINS.left) + ",0)") + .call(yAxis); + + // create horizontal line for avg + svg.append("svg:line") + .attr("x1","50") + .attr("x2","950") + .attr("y1",y(avg)) + .attr("y2",y(avg)) + .style("stroke","red") + .attr("style","stroke-dasharray:2 2; stroke:red") + ; + + + // Now lets "loop" over the data and show the dots + var lines = svg.append("svg:g").attr("class","linechart") + .attr("opacity","0"); + + var line = d3.svg.line() + .x(function(d,i) { return x(d.timeStamp); }) + .y(function(d) { return y(d.value); }); + + lines.append("svg:path").attr("d", line(jsondata)); + + + var dot = svg.append("svg:g").attr("class","dotschart") + .selectAll("dot").data(jsondata); + + dot.enter().append("svg:circle") + .attr("cx", function (d) { + return x(d.timeStamp); + }) + .attr("cy", function (d) { + return y(d.value); + }) + .style("stroke", "blue") + .attr("r", 1) + + + }); + }) +}); + +function transitionDots() { + $(".linechart").attr("opacity",0); + $(".dotschart").attr("opacity",1); +} + +function transitionLine() { + $(".linechart").attr("opacity",1); + $(".dotschart").attr("opacity",0); +} + </script> + </body> +</html>
commit 625f20d6922eaa4cad999cb583821a90ba9cef44 Author: Heiko W. Rupp hwr@redhat.com Date: Thu Jan 5 22:18:14 2012 +0100
Upgrade D3.js to version 2.7.1
diff --git a/modules/enterprise/gui/rest-war/src/main/webapp/js/d3.csv.js b/modules/enterprise/gui/rest-war/src/main/webapp/js/d3.csv.js new file mode 100644 index 0000000..7565b83 --- /dev/null +++ b/modules/enterprise/gui/rest-war/src/main/webapp/js/d3.csv.js @@ -0,0 +1,92 @@ +(function(){d3.csv = function(url, callback) { + d3.text(url, "text/csv", function(text) { + callback(text && d3.csv.parse(text)); + }); +}; +d3.csv.parse = function(text) { + var header; + return d3.csv.parseRows(text, function(row, i) { + if (i) { + var o = {}, j = -1, m = header.length; + while (++j < m) o[header[j]] = row[j]; + return o; + } else { + header = row; + return null; + } + }); +}; + +d3.csv.parseRows = function(text, f) { + var EOL = {}, // sentinel value for end-of-line + EOF = {}, // sentinel value for end-of-file + rows = [], // output rows + re = /\r\n|[,\r\n]/g, // field separator regex + n = 0, // the current line number + t, // the current token + eol; // is the current token followed by EOL? + + re.lastIndex = 0; // work-around bug in FF 3.6 + + /** @private Returns the next token. */ + function token() { + if (re.lastIndex >= text.length) return EOF; // special case: end of file + if (eol) { eol = false; return EOL; } // special case: end of line + + // special case: quotes + var j = re.lastIndex; + if (text.charCodeAt(j) === 34) { + var i = j; + while (i++ < text.length) { + if (text.charCodeAt(i) === 34) { + if (text.charCodeAt(i + 1) !== 34) break; + i++; + } + } + re.lastIndex = i + 2; + var c = text.charCodeAt(i + 1); + if (c === 13) { + eol = true; + if (text.charCodeAt(i + 2) === 10) re.lastIndex++; + } else if (c === 10) { + eol = true; + } + return text.substring(j + 1, i).replace(/""/g, """); + } + + // common case + var m = re.exec(text); + if (m) { + eol = m[0].charCodeAt(0) !== 44; + return text.substring(j, m.index); + } + re.lastIndex = text.length; + return text.substring(j); + } + + while ((t = token()) !== EOF) { + var a = []; + while ((t !== EOL) && (t !== EOF)) { + a.push(t); + t = token(); + } + if (f && !(a = f(a, n++))) continue; + rows.push(a); + } + + return rows; +}; +d3.csv.format = function(rows) { + return rows.map(d3_csv_formatRow).join("\n"); +}; + +function d3_csv_formatRow(row) { + return row.map(d3_csv_formatValue).join(","); +} + +function d3_csv_formatValue(text) { + return /[",\n]/.test(text) + ? """ + text.replace(/"/g, """") + """ + : text; +} +})(); diff --git a/modules/enterprise/gui/rest-war/src/main/webapp/js/d3.geo.js b/modules/enterprise/gui/rest-war/src/main/webapp/js/d3.geo.js new file mode 100644 index 0000000..2b40252 --- /dev/null +++ b/modules/enterprise/gui/rest-war/src/main/webapp/js/d3.geo.js @@ -0,0 +1,938 @@ +(function(){d3.geo = {}; + +var d3_geo_radians = Math.PI / 180; +// TODO clip input coordinates on opposite hemisphere +d3.geo.azimuthal = function() { + var mode = "orthographic", // or stereographic, gnomonic, equidistant or equalarea + origin, + scale = 200, + translate = [480, 250], + x0, + y0, + cy0, + sy0; + + function azimuthal(coordinates) { + var x1 = coordinates[0] * d3_geo_radians - x0, + y1 = coordinates[1] * d3_geo_radians, + cx1 = Math.cos(x1), + sx1 = Math.sin(x1), + cy1 = Math.cos(y1), + sy1 = Math.sin(y1), + cc = mode !== "orthographic" ? sy0 * sy1 + cy0 * cy1 * cx1 : null, + c, + k = mode === "stereographic" ? 1 / (1 + cc) + : mode === "gnomonic" ? 1 / cc + : mode === "equidistant" ? (c = Math.acos(cc), c ? c / Math.sin(c) : 0) + : mode === "equalarea" ? Math.sqrt(2 / (1 + cc)) + : 1, + x = k * cy1 * sx1, + y = k * (sy0 * cy1 * cx1 - cy0 * sy1); + return [ + scale * x + translate[0], + scale * y + translate[1] + ]; + } + + azimuthal.invert = function(coordinates) { + var x = (coordinates[0] - translate[0]) / scale, + y = (coordinates[1] - translate[1]) / scale, + p = Math.sqrt(x * x + y * y), + c = mode === "stereographic" ? 2 * Math.atan(p) + : mode === "gnomonic" ? Math.atan(p) + : mode === "equidistant" ? p + : mode === "equalarea" ? 2 * Math.asin(.5 * p) + : Math.asin(p), + sc = Math.sin(c), + cc = Math.cos(c); + return [ + (x0 + Math.atan2(x * sc, p * cy0 * cc + y * sy0 * sc)) / d3_geo_radians, + Math.asin(cc * sy0 - (p ? (y * sc * cy0) / p : 0)) / d3_geo_radians + ]; + }; + + azimuthal.mode = function(x) { + if (!arguments.length) return mode; + mode = x + ""; + return azimuthal; + }; + + azimuthal.origin = function(x) { + if (!arguments.length) return origin; + origin = x; + x0 = origin[0] * d3_geo_radians; + y0 = origin[1] * d3_geo_radians; + cy0 = Math.cos(y0); + sy0 = Math.sin(y0); + return azimuthal; + }; + + azimuthal.scale = function(x) { + if (!arguments.length) return scale; + scale = +x; + return azimuthal; + }; + + azimuthal.translate = function(x) { + if (!arguments.length) return translate; + translate = [+x[0], +x[1]]; + return azimuthal; + }; + + return azimuthal.origin([0, 0]); +}; +// Derived from Tom Carden's Albers implementation for Protovis. +// http://gist.github.com/476238 +// http://mathworld.wolfram.com/AlbersEqual-AreaConicProjection.html + +d3.geo.albers = function() { + var origin = [-98, 38], + parallels = [29.5, 45.5], + scale = 1000, + translate = [480, 250], + lng0, // d3_geo_radians * origin[0] + n, + C, + p0; + + function albers(coordinates) { + var t = n * (d3_geo_radians * coordinates[0] - lng0), + p = Math.sqrt(C - 2 * n * Math.sin(d3_geo_radians * coordinates[1])) / n; + return [ + scale * p * Math.sin(t) + translate[0], + scale * (p * Math.cos(t) - p0) + translate[1] + ]; + } + + albers.invert = function(coordinates) { + var x = (coordinates[0] - translate[0]) / scale, + y = (coordinates[1] - translate[1]) / scale, + p0y = p0 + y, + t = Math.atan2(x, p0y), + p = Math.sqrt(x * x + p0y * p0y); + return [ + (lng0 + t / n) / d3_geo_radians, + Math.asin((C - p * p * n * n) / (2 * n)) / d3_geo_radians + ]; + }; + + function reload() { + var phi1 = d3_geo_radians * parallels[0], + phi2 = d3_geo_radians * parallels[1], + lat0 = d3_geo_radians * origin[1], + s = Math.sin(phi1), + c = Math.cos(phi1); + lng0 = d3_geo_radians * origin[0]; + n = .5 * (s + Math.sin(phi2)); + C = c * c + 2 * n * s; + p0 = Math.sqrt(C - 2 * n * Math.sin(lat0)) / n; + return albers; + } + + albers.origin = function(x) { + if (!arguments.length) return origin; + origin = [+x[0], +x[1]]; + return reload(); + }; + + albers.parallels = function(x) { + if (!arguments.length) return parallels; + parallels = [+x[0], +x[1]]; + return reload(); + }; + + albers.scale = function(x) { + if (!arguments.length) return scale; + scale = +x; + return albers; + }; + + albers.translate = function(x) { + if (!arguments.length) return translate; + translate = [+x[0], +x[1]]; + return albers; + }; + + return reload(); +}; + +// A composite projection for the United States, 960x500. The set of standard +// parallels for each region comes from USGS, which is published here: +// http://egsc.usgs.gov/isb/pubs/MapProjections/projections.html#albers +// TODO allow the composite projection to be rescaled? +d3.geo.albersUsa = function() { + var lower48 = d3.geo.albers(); + + var alaska = d3.geo.albers() + .origin([-160, 60]) + .parallels([55, 65]); + + var hawaii = d3.geo.albers() + .origin([-160, 20]) + .parallels([8, 18]); + + var puertoRico = d3.geo.albers() + .origin([-60, 10]) + .parallels([8, 18]); + + function albersUsa(coordinates) { + var lon = coordinates[0], + lat = coordinates[1]; + return (lat > 50 ? alaska + : lon < -140 ? hawaii + : lat < 21 ? puertoRico + : lower48)(coordinates); + } + + albersUsa.scale = function(x) { + if (!arguments.length) return lower48.scale(); + lower48.scale(x); + alaska.scale(x * .6); + hawaii.scale(x); + puertoRico.scale(x * 1.5); + return albersUsa.translate(lower48.translate()); + }; + + albersUsa.translate = function(x) { + if (!arguments.length) return lower48.translate(); + var dz = lower48.scale() / 1000, + dx = x[0], + dy = x[1]; + lower48.translate(x); + alaska.translate([dx - 400 * dz, dy + 170 * dz]); + hawaii.translate([dx - 190 * dz, dy + 200 * dz]); + puertoRico.translate([dx + 580 * dz, dy + 430 * dz]); + return albersUsa; + }; + + return albersUsa.scale(lower48.scale()); +}; +d3.geo.bonne = function() { + var scale = 200, + translate = [480, 250], + x0, // origin longitude in radians + y0, // origin latitude in radians + y1, // parallel latitude in radians + c1; // cot(y1) + + function bonne(coordinates) { + var x = coordinates[0] * d3_geo_radians - x0, + y = coordinates[1] * d3_geo_radians - y0; + if (y1) { + var p = c1 + y1 - y, E = x * Math.cos(y) / p; + x = p * Math.sin(E); + y = p * Math.cos(E) - c1; + } else { + x *= Math.cos(y); + y *= -1; + } + return [ + scale * x + translate[0], + scale * y + translate[1] + ]; + } + + bonne.invert = function(coordinates) { + var x = (coordinates[0] - translate[0]) / scale, + y = (coordinates[1] - translate[1]) / scale; + if (y1) { + var c = c1 + y, p = Math.sqrt(x * x + c * c); + y = c1 + y1 - p; + x = x0 + p * Math.atan2(x, c) / Math.cos(y); + } else { + y *= -1; + x /= Math.cos(y); + } + return [ + x / d3_geo_radians, + y / d3_geo_radians + ]; + }; + + // 90° for Werner, 0° for Sinusoidal + bonne.parallel = function(x) { + if (!arguments.length) return y1 / d3_geo_radians; + c1 = 1 / Math.tan(y1 = x * d3_geo_radians); + return bonne; + }; + + bonne.origin = function(x) { + if (!arguments.length) return [x0 / d3_geo_radians, y0 / d3_geo_radians]; + x0 = x[0] * d3_geo_radians; + y0 = x[1] * d3_geo_radians; + return bonne; + }; + + bonne.scale = function(x) { + if (!arguments.length) return scale; + scale = +x; + return bonne; + }; + + bonne.translate = function(x) { + if (!arguments.length) return translate; + translate = [+x[0], +x[1]]; + return bonne; + }; + + return bonne.origin([0, 0]).parallel(45); +}; +d3.geo.equirectangular = function() { + var scale = 500, + translate = [480, 250]; + + function equirectangular(coordinates) { + var x = coordinates[0] / 360, + y = -coordinates[1] / 360; + return [ + scale * x + translate[0], + scale * y + translate[1] + ]; + } + + equirectangular.invert = function(coordinates) { + var x = (coordinates[0] - translate[0]) / scale, + y = (coordinates[1] - translate[1]) / scale; + return [ + 360 * x, + -360 * y + ]; + }; + + equirectangular.scale = function(x) { + if (!arguments.length) return scale; + scale = +x; + return equirectangular; + }; + + equirectangular.translate = function(x) { + if (!arguments.length) return translate; + translate = [+x[0], +x[1]]; + return equirectangular; + }; + + return equirectangular; +}; +d3.geo.mercator = function() { + var scale = 500, + translate = [480, 250]; + + function mercator(coordinates) { + var x = coordinates[0] / 360, + y = -(Math.log(Math.tan(Math.PI / 4 + coordinates[1] * d3_geo_radians / 2)) / d3_geo_radians) / 360; + return [ + scale * x + translate[0], + scale * Math.max(-.5, Math.min(.5, y)) + translate[1] + ]; + } + + mercator.invert = function(coordinates) { + var x = (coordinates[0] - translate[0]) / scale, + y = (coordinates[1] - translate[1]) / scale; + return [ + 360 * x, + 2 * Math.atan(Math.exp(-360 * y * d3_geo_radians)) / d3_geo_radians - 90 + ]; + }; + + mercator.scale = function(x) { + if (!arguments.length) return scale; + scale = +x; + return mercator; + }; + + mercator.translate = function(x) { + if (!arguments.length) return translate; + translate = [+x[0], +x[1]]; + return mercator; + }; + + return mercator; +}; +function d3_geo_type(types, defaultValue) { + return function(object) { + return object && object.type in types ? types[object.type](object) : defaultValue; + }; +} +/** + * Returns a function that, given a GeoJSON object (e.g., a feature), returns + * the corresponding SVG path. The function can be customized by overriding the + * projection. Point features are mapped to circles with a default radius of + * 4.5px; the radius can be specified either as a constant or a function that + * is evaluated per object. + */ +d3.geo.path = function() { + var pointRadius = 4.5, + pointCircle = d3_path_circle(pointRadius), + projection = d3.geo.albersUsa(); + + function path(d, i) { + if (typeof pointRadius === "function") { + pointCircle = d3_path_circle(pointRadius.apply(this, arguments)); + } + return pathType(d) || null; + } + + function project(coordinates) { + return projection(coordinates).join(","); + } + + var pathType = d3_geo_type({ + + FeatureCollection: function(o) { + var path = [], + features = o.features, + i = -1, // features.index + n = features.length; + while (++i < n) path.push(pathType(features[i].geometry)); + return path.join(""); + }, + + Feature: function(o) { + return pathType(o.geometry); + }, + + Point: function(o) { + return "M" + project(o.coordinates) + pointCircle; + }, + + MultiPoint: function(o) { + var path = [], + coordinates = o.coordinates, + i = -1, // coordinates.index + n = coordinates.length; + while (++i < n) path.push("M", project(coordinates[i]), pointCircle); + return path.join(""); + }, + + LineString: function(o) { + var path = ["M"], + coordinates = o.coordinates, + i = -1, // coordinates.index + n = coordinates.length; + while (++i < n) path.push(project(coordinates[i]), "L"); + path.pop(); + return path.join(""); + }, + + MultiLineString: function(o) { + var path = [], + coordinates = o.coordinates, + i = -1, // coordinates.index + n = coordinates.length, + subcoordinates, // coordinates[i] + j, // subcoordinates.index + m; // subcoordinates.length + while (++i < n) { + subcoordinates = coordinates[i]; + j = -1; + m = subcoordinates.length; + path.push("M"); + while (++j < m) path.push(project(subcoordinates[j]), "L"); + path.pop(); + } + return path.join(""); + }, + + Polygon: function(o) { + var path = [], + coordinates = o.coordinates, + i = -1, // coordinates.index + n = coordinates.length, + subcoordinates, // coordinates[i] + j, // subcoordinates.index + m; // subcoordinates.length + while (++i < n) { + subcoordinates = coordinates[i]; + j = -1; + if ((m = subcoordinates.length - 1) > 0) { + path.push("M"); + while (++j < m) path.push(project(subcoordinates[j]), "L"); + path[path.length - 1] = "Z"; + } + } + return path.join(""); + }, + + MultiPolygon: function(o) { + var path = [], + coordinates = o.coordinates, + i = -1, // coordinates index + n = coordinates.length, + subcoordinates, // coordinates[i] + j, // subcoordinates index + m, // subcoordinates.length + subsubcoordinates, // subcoordinates[j] + k, // subsubcoordinates index + p; // subsubcoordinates.length + while (++i < n) { + subcoordinates = coordinates[i]; + j = -1; + m = subcoordinates.length; + while (++j < m) { + subsubcoordinates = subcoordinates[j]; + k = -1; + if ((p = subsubcoordinates.length - 1) > 0) { + path.push("M"); + while (++k < p) path.push(project(subsubcoordinates[k]), "L"); + path[path.length - 1] = "Z"; + } + } + } + return path.join(""); + }, + + GeometryCollection: function(o) { + var path = [], + geometries = o.geometries, + i = -1, // geometries index + n = geometries.length; + while (++i < n) path.push(pathType(geometries[i])); + return path.join(""); + } + + }); + + var areaType = path.area = d3_geo_type({ + + FeatureCollection: function(o) { + var area = 0, + features = o.features, + i = -1, // features.index + n = features.length; + while (++i < n) area += areaType(features[i]); + return area; + }, + + Feature: function(o) { + return areaType(o.geometry); + }, + + Polygon: function(o) { + return polygonArea(o.coordinates); + }, + + MultiPolygon: function(o) { + var sum = 0, + coordinates = o.coordinates, + i = -1, // coordinates index + n = coordinates.length; + while (++i < n) sum += polygonArea(coordinates[i]); + return sum; + }, + + GeometryCollection: function(o) { + var sum = 0, + geometries = o.geometries, + i = -1, // geometries index + n = geometries.length; + while (++i < n) sum += areaType(geometries[i]); + return sum; + } + + }, 0); + + function polygonArea(coordinates) { + var sum = area(coordinates[0]), // exterior ring + i = 0, // coordinates.index + n = coordinates.length; + while (++i < n) sum -= area(coordinates[i]); // holes + return sum; + } + + function polygonCentroid(coordinates) { + var polygon = d3.geom.polygon(coordinates[0].map(projection)), // exterior ring + area = polygon.area(), + centroid = polygon.centroid(area < 0 ? (area *= -1, 1) : -1), + x = centroid[0], + y = centroid[1], + z = area, + i = 0, // coordinates index + n = coordinates.length; + while (++i < n) { + polygon = d3.geom.polygon(coordinates[i].map(projection)); // holes + area = polygon.area(); + centroid = polygon.centroid(area < 0 ? (area *= -1, 1) : -1); + x -= centroid[0]; + y -= centroid[1]; + z -= area; + } + return [x, y, 6 * z]; // weighted centroid + } + + var centroidType = path.centroid = d3_geo_type({ + + // TODO FeatureCollection + // TODO Point + // TODO MultiPoint + // TODO LineString + // TODO MultiLineString + // TODO GeometryCollection + + Feature: function(o) { + return centroidType(o.geometry); + }, + + Polygon: function(o) { + var centroid = polygonCentroid(o.coordinates); + return [centroid[0] / centroid[2], centroid[1] / centroid[2]]; + }, + + MultiPolygon: function(o) { + var area = 0, + coordinates = o.coordinates, + centroid, + x = 0, + y = 0, + z = 0, + i = -1, // coordinates index + n = coordinates.length; + while (++i < n) { + centroid = polygonCentroid(coordinates[i]); + x += centroid[0]; + y += centroid[1]; + z += centroid[2]; + } + return [x / z, y / z]; + } + + }); + + function area(coordinates) { + return Math.abs(d3.geom.polygon(coordinates.map(projection)).area()); + } + + path.projection = function(x) { + projection = x; + return path; + }; + + path.pointRadius = function(x) { + if (typeof x === "function") pointRadius = x; + else { + pointRadius = +x; + pointCircle = d3_path_circle(pointRadius); + } + return path; + }; + + return path; +}; + +function d3_path_circle(radius) { + return "m0," + radius + + "a" + radius + "," + radius + " 0 1,1 0," + (-2 * radius) + + "a" + radius + "," + radius + " 0 1,1 0," + (+2 * radius) + + "z"; +} +/** + * Given a GeoJSON object, returns the corresponding bounding box. The bounding + * box is represented by a two-dimensional array: [[left, bottom], [right, + * top]], where left is the minimum longitude, bottom is the minimum latitude, + * right is maximum longitude, and top is the maximum latitude. + */ +d3.geo.bounds = function(feature) { + var left = Infinity, + bottom = Infinity, + right = -Infinity, + top = -Infinity; + d3_geo_bounds(feature, function(x, y) { + if (x < left) left = x; + if (x > right) right = x; + if (y < bottom) bottom = y; + if (y > top) top = y; + }); + return [[left, bottom], [right, top]]; +}; + +function d3_geo_bounds(o, f) { + if (o.type in d3_geo_boundsTypes) d3_geo_boundsTypes[o.type](o, f); +} + +var d3_geo_boundsTypes = { + Feature: d3_geo_boundsFeature, + FeatureCollection: d3_geo_boundsFeatureCollection, + GeometryCollection: d3_geo_boundsGeometryCollection, + LineString: d3_geo_boundsLineString, + MultiLineString: d3_geo_boundsMultiLineString, + MultiPoint: d3_geo_boundsLineString, + MultiPolygon: d3_geo_boundsMultiPolygon, + Point: d3_geo_boundsPoint, + Polygon: d3_geo_boundsPolygon +}; + +function d3_geo_boundsFeature(o, f) { + d3_geo_bounds(o.geometry, f); +} + +function d3_geo_boundsFeatureCollection(o, f) { + for (var a = o.features, i = 0, n = a.length; i < n; i++) { + d3_geo_bounds(a[i].geometry, f); + } +} + +function d3_geo_boundsGeometryCollection(o, f) { + for (var a = o.geometries, i = 0, n = a.length; i < n; i++) { + d3_geo_bounds(a[i], f); + } +} + +function d3_geo_boundsLineString(o, f) { + for (var a = o.coordinates, i = 0, n = a.length; i < n; i++) { + f.apply(null, a[i]); + } +} + +function d3_geo_boundsMultiLineString(o, f) { + for (var a = o.coordinates, i = 0, n = a.length; i < n; i++) { + for (var b = a[i], j = 0, m = b.length; j < m; j++) { + f.apply(null, b[j]); + } + } +} + +function d3_geo_boundsMultiPolygon(o, f) { + for (var a = o.coordinates, i = 0, n = a.length; i < n; i++) { + for (var b = a[i][0], j = 0, m = b.length; j < m; j++) { + f.apply(null, b[j]); + } + } +} + +function d3_geo_boundsPoint(o, f) { + f.apply(null, o.coordinates); +} + +function d3_geo_boundsPolygon(o, f) { + for (var a = o.coordinates[0], i = 0, n = a.length; i < n; i++) { + f.apply(null, a[i]); + } +} +// TODO breakAtDateLine? + +d3.geo.circle = function() { + var origin = [0, 0], + degrees = 90 - 1e-2, + radians = degrees * d3_geo_radians, + arc = d3.geo.greatArc().target(Object); + + function circle() { + // TODO render a circle as a Polygon + } + + function visible(point) { + return arc.distance(point) < radians; + } + + circle.clip = function(d) { + arc.source(typeof origin === "function" ? origin.apply(this, arguments) : origin); + return clipType(d); + }; + + var clipType = d3_geo_type({ + + FeatureCollection: function(o) { + var features = o.features.map(clipType).filter(Object); + return features && (o = Object.create(o), o.features = features, o); + }, + + Feature: function(o) { + var geometry = clipType(o.geometry); + return geometry && (o = Object.create(o), o.geometry = geometry, o); + }, + + Point: function(o) { + return visible(o.coordinates) && o; + }, + + MultiPoint: function(o) { + var coordinates = o.coordinates.filter(visible); + return coordinates.length && { + type: o.type, + coordinates: coordinates + }; + }, + + LineString: function(o) { + var coordinates = clip(o.coordinates); + return coordinates.length && (o = Object.create(o), o.coordinates = coordinates, o); + }, + + MultiLineString: function(o) { + var coordinates = o.coordinates.map(clip).filter(function(d) { return d.length; }); + return coordinates.length && (o = Object.create(o), o.coordinates = coordinates, o); + }, + + Polygon: function(o) { + var coordinates = o.coordinates.map(clip); + return coordinates[0].length && (o = Object.create(o), o.coordinates = coordinates, o); + }, + + MultiPolygon: function(o) { + var coordinates = o.coordinates.map(function(d) { return d.map(clip); }).filter(function(d) { return d[0].length; }); + return coordinates.length && (o = Object.create(o), o.coordinates = coordinates, o); + }, + + GeometryCollection: function(o) { + var geometries = o.geometries.map(clipType).filter(Object); + return geometries.length && (o = Object.create(o), o.geometries = geometries, o); + } + + }); + + function clip(coordinates) { + var i = -1, + n = coordinates.length, + clipped = [], + p0, + p1, + p2, + d0, + d1; + + while (++i < n) { + d1 = arc.distance(p2 = coordinates[i]); + if (d1 < radians) { + if (p1) clipped.push(d3_geo_greatArcInterpolate(p1, p2)((d0 - radians) / (d0 - d1))); + clipped.push(p2); + p0 = p1 = null; + } else { + p1 = p2; + if (!p0 && clipped.length) { + clipped.push(d3_geo_greatArcInterpolate(clipped[clipped.length - 1], p1)((radians - d0) / (d1 - d0))); + p0 = p1; + } + } + d0 = d1; + } + + if (p1 && clipped.length) { + d1 = arc.distance(p2 = clipped[0]); + clipped.push(d3_geo_greatArcInterpolate(p1, p2)((d0 - radians) / (d0 - d1))); + } + + return resample(clipped); + } + + // Resample coordinates, creating great arcs between each. + function resample(coordinates) { + var i = 0, + n = coordinates.length, + j, + m, + resampled = n ? [coordinates[0]] : coordinates, + resamples, + origin = arc.source(); + + while (++i < n) { + resamples = arc.source(coordinates[i - 1])(coordinates[i]).coordinates; + for (j = 0, m = resamples.length; ++j < m;) resampled.push(resamples[j]); + } + + arc.source(origin); + return resampled; + } + + circle.origin = function(x) { + if (!arguments.length) return origin; + origin = x; + return circle; + }; + + circle.angle = function(x) { + if (!arguments.length) return degrees; + radians = (degrees = +x) * d3_geo_radians; + return circle; + }; + + // Precision is specified in degrees. + circle.precision = function(x) { + if (!arguments.length) return arc.precision(); + arc.precision(x); + return circle; + }; + + return circle; +} +d3.geo.greatArc = function() { + var source = d3_geo_greatArcSource, + target = d3_geo_greatArcTarget, + precision = 6 * d3_geo_radians; + + function greatArc() { + var a = typeof source === "function" ? source.apply(this, arguments) : source, + b = typeof target === "function" ? target.apply(this, arguments) : target, + i = d3_geo_greatArcInterpolate(a, b), + dt = precision / i.d, + t = 0, + coordinates = [a]; + while ((t += dt) < 1) coordinates.push(i(t)); + coordinates.push(b); + return { + type: "LineString", + coordinates: coordinates + }; + } + + // Length returned in radians; multiply by radius for distance. + greatArc.distance = function() { + var a = typeof source === "function" ? source.apply(this, arguments) : source, + b = typeof target === "function" ? target.apply(this, arguments) : target; + return d3_geo_greatArcInterpolate(a, b).d; + }; + + greatArc.source = function(x) { + if (!arguments.length) return source; + source = x; + return greatArc; + }; + + greatArc.target = function(x) { + if (!arguments.length) return target; + target = x; + return greatArc; + }; + + // Precision is specified in degrees. + greatArc.precision = function(x) { + if (!arguments.length) return precision / d3_geo_radians; + precision = x * d3_geo_radians; + return greatArc; + }; + + return greatArc; +}; + +function d3_geo_greatArcSource(d) { + return d.source; +} + +function d3_geo_greatArcTarget(d) { + return d.target; +} + +function d3_geo_greatArcInterpolate(a, b) { + var x0 = a[0] * d3_geo_radians, cx0 = Math.cos(x0), sx0 = Math.sin(x0), + y0 = a[1] * d3_geo_radians, cy0 = Math.cos(y0), sy0 = Math.sin(y0), + x1 = b[0] * d3_geo_radians, cx1 = Math.cos(x1), sx1 = Math.sin(x1), + y1 = b[1] * d3_geo_radians, cy1 = Math.cos(y1), sy1 = Math.sin(y1), + d = interpolate.d = Math.acos(Math.max(-1, Math.min(1, sy0 * sy1 + cy0 * cy1 * Math.cos(x1 - x0)))), + sd = Math.sin(d); + + // From http://williams.best.vwh.net/avform.htm#Intermediate + function interpolate(t) { + var A = Math.sin(d - (t *= d)) / sd, + B = Math.sin(t) / sd, + x = A * cy0 * cx0 + B * cy1 * cx1, + y = A * cy0 * sx0 + B * cy1 * sx1, + z = A * sy0 + B * sy1; + return [ + Math.atan2(y, x) / d3_geo_radians, + Math.atan2(z, Math.sqrt(x * x + y * y)) / d3_geo_radians + ]; + } + + return interpolate; +} +d3.geo.greatCircle = d3.geo.circle; +})(); diff --git a/modules/enterprise/gui/rest-war/src/main/webapp/js/d3.geom.js b/modules/enterprise/gui/rest-war/src/main/webapp/js/d3.geom.js new file mode 100644 index 0000000..d860c2b --- /dev/null +++ b/modules/enterprise/gui/rest-war/src/main/webapp/js/d3.geom.js @@ -0,0 +1,835 @@ +(function(){d3.geom = {}; +/** + * Computes a contour for a given input grid function using the <a + * href="http://en.wikipedia.org/wiki/Marching_squares%22%3Emarching + * squares</a> algorithm. Returns the contour polygon as an array of points. + * + * @param grid a two-input function(x, y) that returns true for values + * inside the contour and false for values outside the contour. + * @param start an optional starting point [x, y] on the grid. + * @returns polygon [[x1, y1], [x2, y2], …] + */ +d3.geom.contour = function(grid, start) { + var s = start || d3_geom_contourStart(grid), // starting point + c = [], // contour polygon + x = s[0], // current x position + y = s[1], // current y position + dx = 0, // next x direction + dy = 0, // next y direction + pdx = NaN, // previous x direction + pdy = NaN, // previous y direction + i = 0; + + do { + // determine marching squares index + i = 0; + if (grid(x-1, y-1)) i += 1; + if (grid(x, y-1)) i += 2; + if (grid(x-1, y )) i += 4; + if (grid(x, y )) i += 8; + + // determine next direction + if (i === 6) { + dx = pdy === -1 ? -1 : 1; + dy = 0; + } else if (i === 9) { + dx = 0; + dy = pdx === 1 ? -1 : 1; + } else { + dx = d3_geom_contourDx[i]; + dy = d3_geom_contourDy[i]; + } + + // update contour polygon + if (dx != pdx && dy != pdy) { + c.push([x, y]); + pdx = dx; + pdy = dy; + } + + x += dx; + y += dy; + } while (s[0] != x || s[1] != y); + + return c; +}; + +// lookup tables for marching directions +var d3_geom_contourDx = [1, 0, 1, 1,-1, 0,-1, 1,0, 0,0,0,-1, 0,-1,NaN], + d3_geom_contourDy = [0,-1, 0, 0, 0,-1, 0, 0,1,-1,1,1, 0,-1, 0,NaN]; + +function d3_geom_contourStart(grid) { + var x = 0, + y = 0; + + // search for a starting point; begin at origin + // and proceed along outward-expanding diagonals + while (true) { + if (grid(x,y)) { + return [x,y]; + } + if (x === 0) { + x = y + 1; + y = 0; + } else { + x = x - 1; + y = y + 1; + } + } +} +/** + * Computes the 2D convex hull of a set of points using Graham's scanning + * algorithm. The algorithm has been implemented as described in Cormen, + * Leiserson, and Rivest's Introduction to Algorithms. The running time of + * this algorithm is O(n log n), where n is the number of input points. + * + * @param vertices [[x1, y1], [x2, y2], …] + * @returns polygon [[x1, y1], [x2, y2], …] + */ +d3.geom.hull = function(vertices) { + if (vertices.length < 3) return []; + + var len = vertices.length, + plen = len - 1, + points = [], + stack = [], + i, j, h = 0, x1, y1, x2, y2, u, v, a, sp; + + // find the starting ref point: leftmost point with the minimum y coord + for (i=1; i<len; ++i) { + if (vertices[i][1] < vertices[h][1]) { + h = i; + } else if (vertices[i][1] == vertices[h][1]) { + h = (vertices[i][0] < vertices[h][0] ? i : h); + } + } + + // calculate polar angles from ref point and sort + for (i=0; i<len; ++i) { + if (i === h) continue; + y1 = vertices[i][1] - vertices[h][1]; + x1 = vertices[i][0] - vertices[h][0]; + points.push({angle: Math.atan2(y1, x1), index: i}); + } + points.sort(function(a, b) { return a.angle - b.angle; }); + + // toss out duplicate angles + a = points[0].angle; + v = points[0].index; + u = 0; + for (i=1; i<plen; ++i) { + j = points[i].index; + if (a == points[i].angle) { + // keep angle for point most distant from the reference + x1 = vertices[v][0] - vertices[h][0]; + y1 = vertices[v][1] - vertices[h][1]; + x2 = vertices[j][0] - vertices[h][0]; + y2 = vertices[j][1] - vertices[h][1]; + if ((x1*x1 + y1*y1) >= (x2*x2 + y2*y2)) { + points[i].index = -1; + } else { + points[u].index = -1; + a = points[i].angle; + u = i; + v = j; + } + } else { + a = points[i].angle; + u = i; + v = j; + } + } + + // initialize the stack + stack.push(h); + for (i=0, j=0; i<2; ++j) { + if (points[j].index !== -1) { + stack.push(points[j].index); + i++; + } + } + sp = stack.length; + + // do graham's scan + for (; j<plen; ++j) { + if (points[j].index === -1) continue; // skip tossed out points + while (!d3_geom_hullCCW(stack[sp-2], stack[sp-1], points[j].index, vertices)) { + --sp; + } + stack[sp++] = points[j].index; + } + + // construct the hull + var poly = []; + for (i=0; i<sp; ++i) { + poly.push(vertices[stack[i]]); + } + return poly; +} + +// are three points in counter-clockwise order? +function d3_geom_hullCCW(i1, i2, i3, v) { + var t, a, b, c, d, e, f; + t = v[i1]; a = t[0]; b = t[1]; + t = v[i2]; c = t[0]; d = t[1]; + t = v[i3]; e = t[0]; f = t[1]; + return ((f-b)*(c-a) - (d-b)*(e-a)) > 0; +} +// Note: requires coordinates to be counterclockwise and convex! +d3.geom.polygon = function(coordinates) { + + coordinates.area = function() { + var i = 0, + n = coordinates.length, + a = coordinates[n - 1][0] * coordinates[0][1], + b = coordinates[n - 1][1] * coordinates[0][0]; + while (++i < n) { + a += coordinates[i - 1][0] * coordinates[i][1]; + b += coordinates[i - 1][1] * coordinates[i][0]; + } + return (b - a) * .5; + }; + + coordinates.centroid = function(k) { + var i = -1, + n = coordinates.length - 1, + x = 0, + y = 0, + a, + b, + c; + if (!arguments.length) k = -1 / (6 * coordinates.area()); + while (++i < n) { + a = coordinates[i]; + b = coordinates[i + 1]; + c = a[0] * b[1] - b[0] * a[1]; + x += (a[0] + b[0]) * c; + y += (a[1] + b[1]) * c; + } + return [x * k, y * k]; + }; + + // The Sutherland-Hodgman clipping algorithm. + coordinates.clip = function(subject) { + var input, + i = -1, + n = coordinates.length, + j, + m, + a = coordinates[n - 1], + b, + c, + d; + while (++i < n) { + input = subject.slice(); + subject.length = 0; + b = coordinates[i]; + c = input[(m = input.length) - 1]; + j = -1; + while (++j < m) { + d = input[j]; + if (d3_geom_polygonInside(d, a, b)) { + if (!d3_geom_polygonInside(c, a, b)) { + subject.push(d3_geom_polygonIntersect(c, d, a, b)); + } + subject.push(d); + } else if (d3_geom_polygonInside(c, a, b)) { + subject.push(d3_geom_polygonIntersect(c, d, a, b)); + } + c = d; + } + a = b; + } + return subject; + }; + + return coordinates; +}; + +function d3_geom_polygonInside(p, a, b) { + return (b[0] - a[0]) * (p[1] - a[1]) < (b[1] - a[1]) * (p[0] - a[0]); +} + +// Intersect two infinite lines cd and ab. +function d3_geom_polygonIntersect(c, d, a, b) { + var x1 = c[0], x2 = d[0], x3 = a[0], x4 = b[0], + y1 = c[1], y2 = d[1], y3 = a[1], y4 = b[1], + x13 = x1 - x3, + x21 = x2 - x1, + x43 = x4 - x3, + y13 = y1 - y3, + y21 = y2 - y1, + y43 = y4 - y3, + ua = (x43 * y13 - y43 * x13) / (y43 * x21 - x43 * y21); + return [x1 + ua * x21, y1 + ua * y21]; +} +// Adapted from Nicolas Garcia Belmonte's JIT implementation: +// http://blog.thejit.org/2010/02/12/voronoi-tessellation/ +// http://blog.thejit.org/assets/voronoijs/voronoi.js +// See lib/jit/LICENSE for details. + +// Notes: +// +// This implementation does not clip the returned polygons, so if you want to +// clip them to a particular shape you will need to do that either in SVG or by +// post-processing with d3.geom.polygon's clip method. +// +// If any vertices are coincident or have NaN positions, the behavior of this +// method is undefined. Most likely invalid polygons will be returned. You +// should filter invalid points, and consolidate coincident points, before +// computing the tessellation. + +/** + * @param vertices [[x1, y1], [x2, y2], …] + * @returns polygons [[[x1, y1], [x2, y2], …], …] + */ +d3.geom.voronoi = function(vertices) { + var polygons = vertices.map(function() { return []; }); + + d3_voronoi_tessellate(vertices, function(e) { + var s1, + s2, + x1, + x2, + y1, + y2; + if (e.a === 1 && e.b >= 0) { + s1 = e.ep.r; + s2 = e.ep.l; + } else { + s1 = e.ep.l; + s2 = e.ep.r; + } + if (e.a === 1) { + y1 = s1 ? s1.y : -1e6; + x1 = e.c - e.b * y1; + y2 = s2 ? s2.y : 1e6; + x2 = e.c - e.b * y2; + } else { + x1 = s1 ? s1.x : -1e6; + y1 = e.c - e.a * x1; + x2 = s2 ? s2.x : 1e6; + y2 = e.c - e.a * x2; + } + var v1 = [x1, y1], + v2 = [x2, y2]; + polygons[e.region.l.index].push(v1, v2); + polygons[e.region.r.index].push(v1, v2); + }); + + // Reconnect the polygon segments into counterclockwise loops. + return polygons.map(function(polygon, i) { + var cx = vertices[i][0], + cy = vertices[i][1]; + polygon.forEach(function(v) { + v.angle = Math.atan2(v[0] - cx, v[1] - cy); + }); + return polygon.sort(function(a, b) { + return a.angle - b.angle; + }).filter(function(d, i) { + return !i || (d.angle - polygon[i - 1].angle > 1e-10); + }); + }); +}; + +var d3_voronoi_opposite = {"l": "r", "r": "l"}; + +function d3_voronoi_tessellate(vertices, callback) { + + var Sites = { + list: vertices + .map(function(v, i) { + return { + index: i, + x: v[0], + y: v[1] + }; + }) + .sort(function(a, b) { + return a.y < b.y ? -1 + : a.y > b.y ? 1 + : a.x < b.x ? -1 + : a.x > b.x ? 1 + : 0; + }), + bottomSite: null + }; + + var EdgeList = { + list: [], + leftEnd: null, + rightEnd: null, + + init: function() { + EdgeList.leftEnd = EdgeList.createHalfEdge(null, "l"); + EdgeList.rightEnd = EdgeList.createHalfEdge(null, "l"); + EdgeList.leftEnd.r = EdgeList.rightEnd; + EdgeList.rightEnd.l = EdgeList.leftEnd; + EdgeList.list.unshift(EdgeList.leftEnd, EdgeList.rightEnd); + }, + + createHalfEdge: function(edge, side) { + return { + edge: edge, + side: side, + vertex: null, + "l": null, + "r": null + }; + }, + + insert: function(lb, he) { + he.l = lb; + he.r = lb.r; + lb.r.l = he; + lb.r = he; + }, + + leftBound: function(p) { + var he = EdgeList.leftEnd; + do { + he = he.r; + } while (he != EdgeList.rightEnd && Geom.rightOf(he, p)); + he = he.l; + return he; + }, + + del: function(he) { + he.l.r = he.r; + he.r.l = he.l; + he.edge = null; + }, + + right: function(he) { + return he.r; + }, + + left: function(he) { + return he.l; + }, + + leftRegion: function(he) { + return he.edge == null + ? Sites.bottomSite + : he.edge.region[he.side]; + }, + + rightRegion: function(he) { + return he.edge == null + ? Sites.bottomSite + : he.edge.region[d3_voronoi_opposite[he.side]]; + } + }; + + var Geom = { + + bisect: function(s1, s2) { + var newEdge = { + region: {"l": s1, "r": s2}, + ep: {"l": null, "r": null} + }; + + var dx = s2.x - s1.x, + dy = s2.y - s1.y, + adx = dx > 0 ? dx : -dx, + ady = dy > 0 ? dy : -dy; + + newEdge.c = s1.x * dx + s1.y * dy + + (dx * dx + dy * dy) * .5; + + if (adx > ady) { + newEdge.a = 1; + newEdge.b = dy / dx; + newEdge.c /= dx; + } else { + newEdge.b = 1; + newEdge.a = dx / dy; + newEdge.c /= dy; + } + + return newEdge; + }, + + intersect: function(el1, el2) { + var e1 = el1.edge, + e2 = el2.edge; + if (!e1 || !e2 || (e1.region.r == e2.region.r)) { + return null; + } + var d = (e1.a * e2.b) - (e1.b * e2.a); + if (Math.abs(d) < 1e-10) { + return null; + } + var xint = (e1.c * e2.b - e2.c * e1.b) / d, + yint = (e2.c * e1.a - e1.c * e2.a) / d, + e1r = e1.region.r, + e2r = e2.region.r, + el, + e; + if ((e1r.y < e2r.y) || + (e1r.y == e2r.y && e1r.x < e2r.x)) { + el = el1; + e = e1; + } else { + el = el2; + e = e2; + } + var rightOfSite = (xint >= e.region.r.x); + if ((rightOfSite && (el.side === "l")) || + (!rightOfSite && (el.side === "r"))) { + return null; + } + return { + x: xint, + y: yint + }; + }, + + rightOf: function(he, p) { + var e = he.edge, + topsite = e.region.r, + rightOfSite = (p.x > topsite.x); + + if (rightOfSite && (he.side === "l")) { + return 1; + } + if (!rightOfSite && (he.side === "r")) { + return 0; + } + if (e.a === 1) { + var dyp = p.y - topsite.y, + dxp = p.x - topsite.x, + fast = 0, + above = 0; + + if ((!rightOfSite && (e.b < 0)) || + (rightOfSite && (e.b >= 0))) { + above = fast = (dyp >= e.b * dxp); + } else { + above = ((p.x + p.y * e.b) > e.c); + if (e.b < 0) { + above = !above; + } + if (!above) { + fast = 1; + } + } + if (!fast) { + var dxs = topsite.x - e.region.l.x; + above = (e.b * (dxp * dxp - dyp * dyp)) < + (dxs * dyp * (1 + 2 * dxp / dxs + e.b * e.b)); + + if (e.b < 0) { + above = !above; + } + } + } else /* e.b == 1 */ { + var yl = e.c - e.a * p.x, + t1 = p.y - yl, + t2 = p.x - topsite.x, + t3 = yl - topsite.y; + + above = (t1 * t1) > (t2 * t2 + t3 * t3); + } + return he.side === "l" ? above : !above; + }, + + endPoint: function(edge, side, site) { + edge.ep[side] = site; + if (!edge.ep[d3_voronoi_opposite[side]]) return; + callback(edge); + }, + + distance: function(s, t) { + var dx = s.x - t.x, + dy = s.y - t.y; + return Math.sqrt(dx * dx + dy * dy); + } + }; + + var EventQueue = { + list: [], + + insert: function(he, site, offset) { + he.vertex = site; + he.ystar = site.y + offset; + for (var i=0, list=EventQueue.list, l=list.length; i<l; i++) { + var next = list[i]; + if (he.ystar > next.ystar || + (he.ystar == next.ystar && + site.x > next.vertex.x)) { + continue; + } else { + break; + } + } + list.splice(i, 0, he); + }, + + del: function(he) { + for (var i=0, ls=EventQueue.list, l=ls.length; i<l && (ls[i] != he); ++i) {} + ls.splice(i, 1); + }, + + empty: function() { return EventQueue.list.length === 0; }, + + nextEvent: function(he) { + for (var i=0, ls=EventQueue.list, l=ls.length; i<l; ++i) { + if (ls[i] == he) return ls[i+1]; + } + return null; + }, + + min: function() { + var elem = EventQueue.list[0]; + return { + x: elem.vertex.x, + y: elem.ystar + }; + }, + + extractMin: function() { + return EventQueue.list.shift(); + } + }; + + EdgeList.init(); + Sites.bottomSite = Sites.list.shift(); + + var newSite = Sites.list.shift(), newIntStar; + var lbnd, rbnd, llbnd, rrbnd, bisector; + var bot, top, temp, p, v; + var e, pm; + + while (true) { + if (!EventQueue.empty()) { + newIntStar = EventQueue.min(); + } + if (newSite && (EventQueue.empty() + || newSite.y < newIntStar.y + || (newSite.y == newIntStar.y + && newSite.x < newIntStar.x))) { //new site is smallest + lbnd = EdgeList.leftBound(newSite); + rbnd = EdgeList.right(lbnd); + bot = EdgeList.rightRegion(lbnd); + e = Geom.bisect(bot, newSite); + bisector = EdgeList.createHalfEdge(e, "l"); + EdgeList.insert(lbnd, bisector); + p = Geom.intersect(lbnd, bisector); + if (p) { + EventQueue.del(lbnd); + EventQueue.insert(lbnd, p, Geom.distance(p, newSite)); + } + lbnd = bisector; + bisector = EdgeList.createHalfEdge(e, "r"); + EdgeList.insert(lbnd, bisector); + p = Geom.intersect(bisector, rbnd); + if (p) { + EventQueue.insert(bisector, p, Geom.distance(p, newSite)); + } + newSite = Sites.list.shift(); + } else if (!EventQueue.empty()) { //intersection is smallest + lbnd = EventQueue.extractMin(); + llbnd = EdgeList.left(lbnd); + rbnd = EdgeList.right(lbnd); + rrbnd = EdgeList.right(rbnd); + bot = EdgeList.leftRegion(lbnd); + top = EdgeList.rightRegion(rbnd); + v = lbnd.vertex; + Geom.endPoint(lbnd.edge, lbnd.side, v); + Geom.endPoint(rbnd.edge, rbnd.side, v); + EdgeList.del(lbnd); + EventQueue.del(rbnd); + EdgeList.del(rbnd); + pm = "l"; + if (bot.y > top.y) { + temp = bot; + bot = top; + top = temp; + pm = "r"; + } + e = Geom.bisect(bot, top); + bisector = EdgeList.createHalfEdge(e, pm); + EdgeList.insert(llbnd, bisector); + Geom.endPoint(e, d3_voronoi_opposite[pm], v); + p = Geom.intersect(llbnd, bisector); + if (p) { + EventQueue.del(llbnd); + EventQueue.insert(llbnd, p, Geom.distance(p, bot)); + } + p = Geom.intersect(bisector, rrbnd); + if (p) { + EventQueue.insert(bisector, p, Geom.distance(p, bot)); + } + } else { + break; + } + }//end while + + for (lbnd = EdgeList.right(EdgeList.leftEnd); + lbnd != EdgeList.rightEnd; + lbnd = EdgeList.right(lbnd)) { + callback(lbnd.edge); + } +} +/** +* @param vertices [[x1, y1], [x2, y2], …] +* @returns triangles [[[x1, y1], [x2, y2], [x3, y3]], …] + */ +d3.geom.delaunay = function(vertices) { + var edges = vertices.map(function() { return []; }), + triangles = []; + + // Use the Voronoi tessellation to determine Delaunay edges. + d3_voronoi_tessellate(vertices, function(e) { + edges[e.region.l.index].push(vertices[e.region.r.index]); + }); + + // Reconnect the edges into counterclockwise triangles. + edges.forEach(function(edge, i) { + var v = vertices[i], + cx = v[0], + cy = v[1]; + edge.forEach(function(v) { + v.angle = Math.atan2(v[0] - cx, v[1] - cy); + }); + edge.sort(function(a, b) { + return a.angle - b.angle; + }); + for (var j = 0, m = edge.length - 1; j < m; j++) { + triangles.push([v, edge[j], edge[j + 1]]); + } + }); + + return triangles; +}; +// Constructs a new quadtree for the specified array of points. A quadtree is a +// two-dimensional recursive spatial subdivision. This implementation uses +// square partitions, dividing each square into four equally-sized squares. Each +// point exists in a unique node; if multiple points are in the same position, +// some points may be stored on internal nodes rather than leaf nodes. Quadtrees +// can be used to accelerate various spatial operations, such as the Barnes-Hut +// approximation for computing n-body forces, or collision detection. +d3.geom.quadtree = function(points, x1, y1, x2, y2) { + var p, + i = -1, + n = points.length; + + // Type conversion for deprecated API. + if (n && isNaN(points[0].x)) points = points.map(d3_geom_quadtreePoint); + + // Allow bounds to be specified explicitly. + if (arguments.length < 5) { + if (arguments.length === 3) { + y2 = x2 = y1; + y1 = x1; + } else { + x1 = y1 = Infinity; + x2 = y2 = -Infinity; + + // Compute bounds. + while (++i < n) { + p = points[i]; + if (p.x < x1) x1 = p.x; + if (p.y < y1) y1 = p.y; + if (p.x > x2) x2 = p.x; + if (p.y > y2) y2 = p.y; + } + + // Squarify the bounds. + var dx = x2 - x1, + dy = y2 - y1; + if (dx > dy) y2 = y1 + dx; + else x2 = x1 + dy; + } + } + + // Recursively inserts the specified point p at the node n or one of its + // descendants. The bounds are defined by [x1, x2] and [y1, y2]. + function insert(n, p, x1, y1, x2, y2) { + if (isNaN(p.x) || isNaN(p.y)) return; // ignore invalid points + if (n.leaf) { + var v = n.point; + if (v) { + // If the point at this leaf node is at the same position as the new + // point we are adding, we leave the point associated with the + // internal node while adding the new point to a child node. This + // avoids infinite recursion. + if ((Math.abs(v.x - p.x) + Math.abs(v.y - p.y)) < .01) { + insertChild(n, p, x1, y1, x2, y2); + } else { + n.point = null; + insertChild(n, v, x1, y1, x2, y2); + insertChild(n, p, x1, y1, x2, y2); + } + } else { + n.point = p; + } + } else { + insertChild(n, p, x1, y1, x2, y2); + } + } + + // Recursively inserts the specified point p into a descendant of node n. The + // bounds are defined by [x1, x2] and [y1, y2]. + function insertChild(n, p, x1, y1, x2, y2) { + // Compute the split point, and the quadrant in which to insert p. + var sx = (x1 + x2) * .5, + sy = (y1 + y2) * .5, + right = p.x >= sx, + bottom = p.y >= sy, + i = (bottom << 1) + right; + + // Recursively insert into the child node. + n.leaf = false; + n = n.nodes[i] || (n.nodes[i] = d3_geom_quadtreeNode()); + + // Update the bounds as we recurse. + if (right) x1 = sx; else x2 = sx; + if (bottom) y1 = sy; else y2 = sy; + insert(n, p, x1, y1, x2, y2); + } + + // Create the root node. + var root = d3_geom_quadtreeNode(); + + root.add = function(p) { + insert(root, p, x1, y1, x2, y2); + }; + + root.visit = function(f) { + d3_geom_quadtreeVisit(f, root, x1, y1, x2, y2); + }; + + // Insert all points. + points.forEach(root.add); + return root; +}; + +function d3_geom_quadtreeNode() { + return { + leaf: true, + nodes: [], + point: null + }; +} + +function d3_geom_quadtreeVisit(f, node, x1, y1, x2, y2) { + if (!f(node, x1, y1, x2, y2)) { + var sx = (x1 + x2) * .5, + sy = (y1 + y2) * .5, + children = node.nodes; + if (children[0]) d3_geom_quadtreeVisit(f, children[0], x1, y1, sx, sy); + if (children[1]) d3_geom_quadtreeVisit(f, children[1], sx, y1, x2, sy); + if (children[2]) d3_geom_quadtreeVisit(f, children[2], x1, sy, sx, y2); + if (children[3]) d3_geom_quadtreeVisit(f, children[3], sx, sy, x2, y2); + } +} + +function d3_geom_quadtreePoint(p) { + return { + x: p[0], + y: p[1] + }; +} +})(); diff --git a/modules/enterprise/gui/rest-war/src/main/webapp/js/d3.js b/modules/enterprise/gui/rest-war/src/main/webapp/js/d3.js index bfbc7ee..77ee372 100755 --- a/modules/enterprise/gui/rest-war/src/main/webapp/js/d3.js +++ b/modules/enterprise/gui/rest-war/src/main/webapp/js/d3.js @@ -1,22 +1,26 @@ -(function(){d3 = {version: "1.29.1"}; // semver -if (!Date.now) Date.now = function() { +(function(){if (!Date.now) Date.now = function() { return +new Date; }; -if (!Object.create) Object.create = function(o) { - /** @constructor */ function f() {} - f.prototype = o; - return new f; -}; +try { + document.createElement("div").style.setProperty("opacity", 0, ""); +} catch (error) { + var d3_style_prototype = CSSStyleDeclaration.prototype, + d3_style_setProperty = d3_style_prototype.setProperty; + d3_style_prototype.setProperty = function(name, value, priority) { + d3_style_setProperty.call(this, name, value + "", priority); + }; +} +d3 = {version: "2.7.1"}; // semver var d3_array = d3_arraySlice; // conversion for NodeLists
-function d3_arrayCopy(psuedoarray) { - var i = -1, n = psuedoarray.length, array = []; - while (++i < n) array.push(psuedoarray[i]); +function d3_arrayCopy(pseudoarray) { + var i = -1, n = pseudoarray.length, array = []; + while (++i < n) array.push(pseudoarray[i]); return array; }
-function d3_arraySlice(psuedoarray) { - return Array.prototype.slice.call(psuedoarray); +function d3_arraySlice(pseudoarray) { + return Array.prototype.slice.call(pseudoarray); }
try { @@ -24,21 +28,63 @@ try { } catch(e) { d3_array = d3_arrayCopy; } + +var d3_arraySubclass = [].__proto__? + +// Until ECMAScript supports array subclassing, prototype injection works well. +function(array, prototype) { + array.__proto__ = prototype; +}: + +// And if your browser doesn't support __proto__, we'll use direct extension. +function(array, prototype) { + for (var property in prototype) array[property] = prototype[property]; +}; +function d3_this() { + return this; +} d3.functor = function(v) { return typeof v === "function" ? v : function() { return v; }; }; -// A getter-setter method that preserves the appropriate `this` context. -d3.rebind = function(object, method) { +// Copies a variable number of methods from source to target. +d3.rebind = function(target, source) { + var i = 1, n = arguments.length, method; + while (++i < n) target[method = arguments[i]] = d3_rebind(target, source, source[method]); + return target; +}; + +// Method is assumed to be a standard D3 getter-setter: +// If passed with no arguments, gets the value. +// If passed with arguments, sets the value and returns the target. +function d3_rebind(target, source, method) { return function() { - var x = method.apply(object, arguments); - return arguments.length ? object : x; + var value = method.apply(source, arguments); + return arguments.length ? target : value; }; -}; +} d3.ascending = function(a, b) { - return a < b ? -1 : a > b ? 1 : 0; + return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN; }; d3.descending = function(a, b) { - return b < a ? -1 : b > a ? 1 : 0; + return b < a ? -1 : b > a ? 1 : b >= a ? 0 : NaN; +}; +d3.mean = function(array, f) { + var n = array.length, + a, + m = 0, + i = -1, + j = 0; + if (arguments.length === 1) { + while (++i < n) if (d3_number(a = array[i])) m += (a - m) / ++j; + } else { + while (++i < n) if (d3_number(a = f.call(array, array[i], i))) m += (a - m) / ++j; + } + return j ? m : undefined; +}; +d3.median = function(array, f) { + if (arguments.length > 1) array = array.map(f); + array = array.filter(d3_number); + return array.length ? d3.quantile(array.sort(d3.ascending), .5) : undefined; }; d3.min = function(array, f) { var i = -1, @@ -68,6 +114,45 @@ d3.max = function(array, f) { } return a; }; +d3.extent = function(array, f) { + var i = -1, + n = array.length, + a, + b, + c; + if (arguments.length === 1) { + while (++i < n && ((a = c = array[i]) == null || a != a)) a = c = undefined; + while (++i < n) if ((b = array[i]) != null) { + if (a > b) a = b; + if (c < b) c = b; + } + } else { + while (++i < n && ((a = c = f.call(array, array[i], i)) == null || a != a)) a = undefined; + while (++i < n) if ((b = f.call(array, array[i], i)) != null) { + if (a > b) a = b; + if (c < b) c = b; + } + } + return [a, c]; +}; +d3.random = { + normal: function(mean, deviation) { + if (arguments.length < 2) deviation = 1; + if (arguments.length < 1) mean = 0; + return function() { + var x, y, r; + do { + x = Math.random() * 2 - 1; + y = Math.random() * 2 - 1; + r = x * x + y * y; + } while (!r || r > 1); + return mean + deviation * x * Math.sqrt(-2 * Math.log(r) / r); + }; + } +}; +function d3_number(x) { + return x != null && !isNaN(x); +} d3.sum = function(array, f) { var s = 0, n = array.length, @@ -90,6 +175,9 @@ d3.quantile = function(values, p) { e = H - h; return e ? v + e * (values[h] - v) : v; }; +d3.transpose = function(matrix) { + return d3.zip.apply(d3, matrix); +}; d3.zip = function() { if (!(n = arguments.length)) return []; for (var i = -1, m = d3.min(arguments, d3_zipLength), zips = new Array(m); ++i < m;) { @@ -303,27 +391,19 @@ function d3_splitter(d) { function d3_collapse(s) { return s.replace(/(^\s+)|(\s+$)/g, "").replace(/\s+/g, " "); } -// -// Note: assigning to the arguments array simultaneously changes the value of -// the corresponding argument! -// -// TODO The `this` argument probably shouldn't be the first argument to the -// callback, anyway, since it's redundant. However, that will require a major -// version bump due to backwards compatibility, so I'm not changing it right -// away. -// -function d3_call(callback) { - callback.apply(this, (arguments[0] = this, arguments)); - return this; -} /** * @param {number} start * @param {number=} stop * @param {number=} step */ d3.range = function(start, stop, step) { - if (arguments.length === 1) { stop = start; start = 0; } - if (step == null) step = 1; + if (arguments.length < 3) { + step = 1; + if (arguments.length < 2) { + stop = start; + start = 0; + } + } if ((stop - start) / step == Infinity) throw new Error("infinite range"); var range = [], i = -1, @@ -336,7 +416,7 @@ d3.requote = function(s) { return s.replace(d3_requote_re, "\$&"); };
-var d3_requote_re = /[\^$*+?[]().{}]/g; +var d3_requote_re = /[\^$*+?|[]().{}]/g; d3.round = function(x, n) { return n ? Math.round(x * Math.pow(10, n)) * Math.pow(10, -n) @@ -344,9 +424,10 @@ d3.round = function(x, n) { }; d3.xhr = function(url, mime, callback) { var req = new XMLHttpRequest; - if (arguments.length < 3) callback = mime; + if (arguments.length < 3) callback = mime, mime = null; else if (mime && req.overrideMimeType) req.overrideMimeType(mime); req.open("GET", url, true); + if (mime) req.setRequestHeader("Accept", mime); req.onreadystatechange = function() { if (req.readyState === 4) callback(req.status < 300 ? req : null); }; @@ -387,66 +468,79 @@ d3.xml = function(url, mime, callback) { } d3.xhr(url, mime, ready); }; -d3.ns = { - - prefix: { - svg: "http://www.w3.org/2000/svg", - xhtml: "http://www.w3.org/1999/xhtml", - xlink: "http://www.w3.org/1999/xlink", - xml: "http://www.w3.org/XML/1998/namespace", - xmlns: "http://www.w3.org/2000/xmlns/" - }, +var d3_nsPrefix = { + svg: "http://www.w3.org/2000/svg", + xhtml: "http://www.w3.org/1999/xhtml", + xlink: "http://www.w3.org/1999/xlink", + xml: "http://www.w3.org/XML/1998/namespace", + xmlns: "http://www.w3.org/2000/xmlns/" +};
+d3.ns = { + prefix: d3_nsPrefix, qualify: function(name) { var i = name.indexOf(":"); - return i < 0 ? name : { - space: d3.ns.prefix[name.substring(0, i)], - local: name.substring(i + 1) - }; + return i < 0 ? (name in d3_nsPrefix + ? {space: d3_nsPrefix[name], local: name} : name) + : {space: d3_nsPrefix[name.substring(0, i)], local: name.substring(i + 1)}; } - }; -/** @param {...string} types */ -d3.dispatch = function(types) { - var dispatch = {}, - type; - for (var i = 0, n = arguments.length; i < n; i++) { - type = arguments[i]; - dispatch[type] = d3_dispatch(type); - } +d3.dispatch = function() { + var dispatch = new d3_dispatch(), + i = -1, + n = arguments.length; + while (++i < n) dispatch[arguments[i]] = d3_dispatch_event(); return dispatch; };
-function d3_dispatch(type) { - var dispatch = {}, - listeners = []; +function d3_dispatch() {}
- dispatch.add = function(listener) { - for (var i = 0; i < listeners.length; i++) { - if (listeners[i].listener == listener) return dispatch; // already registered - } - listeners.push({listener: listener, on: true}); - return dispatch; - }; +d3_dispatch.prototype.on = function(type, listener) { + var i = type.indexOf("."), + name = "";
- dispatch.remove = function(listener) { - for (var i = 0; i < listeners.length; i++) { - var l = listeners[i]; - if (l.listener == listener) { - l.on = false; - listeners = listeners.slice(0, i).concat(listeners.slice(i + 1)); - break; - } + // Extract optional namespace, e.g., "click.foo" + if (i > 0) { + name = type.substring(i + 1); + type = type.substring(0, i); + } + + return arguments.length < 2 + ? this[type].on(name) + : (this[type].on(name, listener), this); +}; + +function d3_dispatch_event() { + var listeners = [], + listenerByName = {}; + + function dispatch() { + var z = listeners, // defensive reference + i = -1, + n = z.length, + l; + while (++i < n) if (l = z[i].on) l.apply(this, arguments); + } + + dispatch.on = function(name, listener) { + var l, i; + + // return the current listener, if any + if (arguments.length < 2) return (l = listenerByName[name]) && l.on; + + // remove the old listener, if any (with copy-on-write) + if (l = listenerByName[name]) { + l.on = null; + listeners = listeners.slice(0, i = listeners.indexOf(l)).concat(listeners.slice(i + 1)); + delete listenerByName[name]; } - return dispatch; - };
- dispatch.dispatch = function() { - var ls = listeners; // defensive reference - for (var i = 0, n = ls.length; i < n; i++) { - var l = ls[i]; - if (l.on) l.listener.apply(this, arguments); + // add the new listener, if any + if (listener) { + listeners.push(listenerByName[name] = {on: listener}); } + + return dispatch; };
return dispatch; @@ -461,10 +555,11 @@ d3.format = function(specifier) { comma = match[7], precision = match[8], type = match[9], - percentage = false, + scale = 1, + suffix = "", integer = false;
- if (precision) precision = precision.substring(1); + if (precision) precision = +precision.substring(1);
if (zfill) { fill = "0"; // TODO align = "="; @@ -473,22 +568,36 @@ d3.format = function(specifier) {
switch (type) { case "n": comma = true; type = "g"; break; - case "%": percentage = true; type = "f"; break; - case "p": percentage = true; type = "r"; break; - case "d": integer = true; precision = "0"; break; + case "%": scale = 100; suffix = "%"; type = "f"; break; + case "p": scale = 100; suffix = "%"; type = "r"; break; + case "d": integer = true; precision = 0; break; + case "s": scale = -1; type = "r"; break; }
+ // If no precision is specified for r, fallback to general notation. + if (type == "r" && !precision) type = "g"; + type = d3_format_types[type] || d3_format_typeDefault;
return function(value) { - var number = percentage ? value * 100 : +value, - negative = (number < 0) && (number = -number) ? "\u2212" : sign;
// Return the empty string for floats formatted as ints. - if (integer && (number % 1)) return ""; + if (integer && (value % 1)) return ""; + + // Convert negative to positive, and record the sign prefix. + var negative = (value < 0) && (value = -value) ? "\u2212" : sign;
- // Convert the input value to the desired precision. - value = type(number, precision); + // Apply the scale, computing it from the value's exponent for si format. + if (scale < 0) { + var prefix = d3.formatPrefix(value, precision); + value *= prefix.scale; + suffix = prefix.symbol; + } else { + value *= scale; + } + + // Convert to the desired precision. + value = type(value, precision);
// If the fill character is 0, the sign and group is applied after the fill. if (zfill) { @@ -505,9 +614,8 @@ d3.format = function(specifier) { var length = value.length; if (length < width) value = new Array(width - length + 1).join(fill) + value; } - if (percentage) value += "%";
- return value; + return value + suffix; }; };
@@ -518,12 +626,13 @@ var d3_format_types = { g: function(x, p) { return x.toPrecision(p); }, e: function(x, p) { return x.toExponential(p); }, f: function(x, p) { return x.toFixed(p); }, - r: function(x, p) { - var n = 1 + Math.floor(1e-15 + Math.log(x) / Math.LN10); - return d3.round(x, p - n).toFixed(Math.max(0, p - n)); - } + r: function(x, p) { return d3.round(x, p = d3_format_precision(x, p)).toFixed(Math.max(0, Math.min(20, p))); } };
+function d3_format_precision(x, p) { + return p - (x ? 1 + Math.floor(Math.log(x + Math.pow(10, 1 + Math.floor(Math.log(x) / Math.LN10) - p)) / Math.LN10) : 1); +} + function d3_format_typeDefault(x) { return x + ""; } @@ -536,6 +645,26 @@ function d3_format_group(value) { while (i > 0) t.push(value.substring(i -= 3, i + 3)); return t.reverse().join(",") + f; } +var d3_formatPrefixes = ["y","z","a","f","p","n","μ","m","","k","M","G","T","P","E","Z","Y"].map(d3_formatPrefix); + +d3.formatPrefix = function(value, precision) { + var i = 0; + if (value) { + if (value < 0) value *= -1; + if (precision) value = d3.round(value, d3_format_precision(value, precision)); + i = 1 + Math.floor(1e-12 + Math.log(value) / Math.LN10); + i = Math.max(-24, Math.min(24, Math.floor((i <= 0 ? i + 1 : i - 1) / 3) * 3)); + } + return d3_formatPrefixes[8 + i / 3]; +}; + +function d3_formatPrefix(d, i) { + return { + scale: Math.pow(10, (8 - i) * 3), + symbol: d + }; +} + /* * TERMS OF USE - EASING EQUATIONS * @@ -598,9 +727,15 @@ d3.ease = function(name) { var i = name.indexOf("-"), t = i >= 0 ? name.substring(0, i) : name, m = i >= 0 ? name.substring(i + 1) : "in"; - return d3_ease_mode[m](d3_ease[t].apply(null, Array.prototype.slice.call(arguments, 1))); + return d3_ease_clamp(d3_ease_mode[m](d3_ease[t].apply(null, Array.prototype.slice.call(arguments, 1)))); };
+function d3_ease_clamp(f) { + return function(t) { + return t <= 0 ? 0 : t >= 1 ? 1 : f(t); + }; +} + function d3_ease_reverse(f) { return function(t) { return 1 - f(1 - t); @@ -628,7 +763,7 @@ function d3_ease_sin(t) { }
function d3_ease_exp(t) { - return t ? Math.pow(2, 10 * (t - 1)) - 1e-3 : 0; + return Math.pow(2, 10 * (t - 1)); }
function d3_ease_circle(t) { @@ -659,6 +794,11 @@ function d3_ease_bounce(t) { : 7.5625 * (t -= 2.625 / 2.75) * t + .984375; } d3.event = null; + +function d3_eventCancel() { + d3.event.stopPropagation(); + d3.event.preventDefault(); +} d3.interpolate = function(a, b) { var i = d3.interpolators.length, f; while (--i >= 0 && !(f = d3.interpolators[i](a, b))); @@ -753,6 +893,10 @@ d3.interpolateString = function(a, b) { }; };
+d3.interpolateTransform = function(a, b) { + return d3.interpolateString(d3.transform(a) + "", d3.transform(b) + ""); +}; + d3.interpolateRgb = function(a, b) { a = d3.rgb(a); b = d3.rgb(b); @@ -763,10 +907,10 @@ d3.interpolateRgb = function(a, b) { bg = b.g - ag, bb = b.b - ab; return function(t) { - return "rgb(" + Math.round(ar + br * t) - + "," + Math.round(ag + bg * t) - + "," + Math.round(ab + bb * t) - + ")"; + return "#" + + d3_rgb_hex(Math.round(ar + br * t)) + + d3_rgb_hex(Math.round(ag + bg * t)) + + d3_rgb_hex(Math.round(ab + bb * t)); }; };
@@ -823,34 +967,34 @@ d3.interpolateObject = function(a, b) { }; }
-var d3_interpolate_number = /[-+]?(?:\d+.\d+|\d+.|.\d+|\d+)(?:[eE][-]?\d+)?/g, - d3_interpolate_rgb = {background: 1, fill: 1, stroke: 1}; +var d3_interpolate_number = /[-+]?(?:\d*.?\d+)(?:[eE][-+]?\d+)?/g;
function d3_interpolateByName(n) { - return n in d3_interpolate_rgb || /\bcolor\b/.test(n) - ? d3.interpolateRgb + return n == "transform" + ? d3.interpolateTransform : d3.interpolate; }
d3.interpolators = [ d3.interpolateObject, function(a, b) { return (b instanceof Array) && d3.interpolateArray(a, b); }, - function(a, b) { return (typeof b === "string") && d3.interpolateString(String(a), b); }, - function(a, b) { return (b in d3_rgb_names || /^(#|rgb(|hsl()/.test(b)) && d3.interpolateRgb(String(a), b); }, - function(a, b) { return (typeof b === "number") && d3.interpolateNumber(+a, b); } + function(a, b) { return (typeof a === "string" || typeof b === "string") && d3.interpolateString(a + "", b + ""); }, + function(a, b) { return (typeof b === "string" ? b in d3_rgb_names || /^(#|rgb(|hsl()/.test(b) : b instanceof d3_Rgb || b instanceof d3_Hsl) && d3.interpolateRgb(a, b); }, + function(a, b) { return !isNaN(a = +a) && !isNaN(b = +b) && d3.interpolateNumber(a, b); } ]; function d3_uninterpolateNumber(a, b) { - b = 1 / (b - (a = +a)); + b = b - (a = +a) ? 1 / (b - a) : 0; return function(x) { return (x - a) * b; }; }
function d3_uninterpolateClamp(a, b) { - b = 1 / (b - (a = +a)); + b = b - (a = +a) ? 1 / (b - a) : 0; return function(x) { return Math.max(0, Math.min(1, (x - a) * b)); }; } d3.rgb = function(r, g, b) { return arguments.length === 1 - ? d3_rgb_parse("" + r, d3_rgb, d3_hsl_rgb) + ? (r instanceof d3_Rgb ? d3_rgb(r.r, r.g, r.b) + : d3_rgb_parse("" + r, d3_rgb, d3_hsl_rgb)) : d3_rgb(~~r, ~~g, ~~b); };
@@ -875,17 +1019,17 @@ d3_Rgb.prototype.brighter = function(k) { if (g && g < i) g = i; if (b && b < i) b = i; return d3_rgb( - Math.min(255, Math.floor(r / k)), - Math.min(255, Math.floor(g / k)), - Math.min(255, Math.floor(b / k))); + Math.min(255, Math.floor(r / k)), + Math.min(255, Math.floor(g / k)), + Math.min(255, Math.floor(b / k))); };
d3_Rgb.prototype.darker = function(k) { k = Math.pow(0.7, arguments.length ? k : 1); return d3_rgb( - Math.max(0, Math.floor(k * this.r)), - Math.max(0, Math.floor(k * this.g)), - Math.max(0, Math.floor(k * this.b))); + Math.floor(k * this.r), + Math.floor(k * this.g), + Math.floor(k * this.b)); };
d3_Rgb.prototype.hsl = function() { @@ -897,7 +1041,9 @@ d3_Rgb.prototype.toString = function() { };
function d3_rgb_hex(v) { - return v < 0x10 ? "0" + v.toString(16) : v.toString(16); + return v < 0x10 + ? "0" + Math.max(0, v).toString(16) + : Math.min(255, v).toString(16); }
function d3_rgb_parse(format, rgb, hsl) { @@ -1134,7 +1280,8 @@ for (var d3_rgb_name in d3_rgb_names) { } d3.hsl = function(h, s, l) { return arguments.length === 1 - ? d3_rgb_parse("" + h, d3_rgb_hsl, d3_hsl) + ? (h instanceof d3_Hsl ? d3_hsl(h.h, h.s, h.l) + : d3_rgb_parse("" + h, d3_rgb_hsl, d3_hsl)) : d3_hsl(+h, +s, +l); };
@@ -1163,7 +1310,7 @@ d3_Hsl.prototype.rgb = function() { };
d3_Hsl.prototype.toString = function() { - return "hsl(" + this.h + "," + this.s * 100 + "%," + this.l * 100 + "%)"; + return this.rgb().toString(); };
function d3_hsl_rgb(h, s, l) { @@ -1194,906 +1341,859 @@ function d3_hsl_rgb(h, s, l) {
return d3_rgb(vv(h + 120), vv(h), vv(h - 120)); } +function d3_selection(groups) { + d3_arraySubclass(groups, d3_selectionPrototype); + return groups; +} + var d3_select = function(s, n) { return n.querySelector(s); }, - d3_selectAll = function(s, n) { return d3_array(n.querySelectorAll(s)); }; + d3_selectAll = function(s, n) { return n.querySelectorAll(s); }, + d3_selectRoot = document.documentElement, + d3_selectMatcher = d3_selectRoot.matchesSelector || d3_selectRoot.webkitMatchesSelector || d3_selectRoot.mozMatchesSelector || d3_selectRoot.msMatchesSelector || d3_selectRoot.oMatchesSelector, + d3_selectMatches = function(n, s) { return d3_selectMatcher.call(n, s); };
-// Use Sizzle, if available. +// Prefer Sizzle, if available. if (typeof Sizzle === "function") { d3_select = function(s, n) { return Sizzle(s, n)[0]; }; d3_selectAll = function(s, n) { return Sizzle.uniqueSort(Sizzle(s, n)); }; + d3_selectMatches = Sizzle.matchesSelector; }
-var d3_root = d3_selection([[document]]); -d3_root[0].parentNode = document.documentElement; - -// TODO fast singleton implementation! -d3.select = function(selector) { - return typeof selector === "string" - ? d3_root.select(selector) - : d3_selection([[selector]]); // assume node -}; +var d3_selectionPrototype = [];
-d3.selectAll = function(selector) { - return typeof selector === "string" - ? d3_root.selectAll(selector) - : d3_selection([d3_array(selector)]); // assume node[] +d3.selection = function() { + return d3_selectionRoot; };
-function d3_selection(groups) { - - function select(select) { - var subgroups = [], - subgroup, - subnode, - group, - node; - for (var j = 0, m = groups.length; j < m; j++) { - group = groups[j]; - subgroups.push(subgroup = []); - subgroup.parentNode = group.parentNode; - for (var i = 0, n = group.length; i < n; i++) { - if (node = group[i]) { - subgroup.push(subnode = select(node)); - if (subnode && "__data__" in node) subnode.__data__ = node.__data__; - } else { - subgroup.push(null); - } - } - } - return d3_selection(subgroups); - } - - function selectAll(selectAll) { - var subgroups = [], - subgroup, - group, - node; - for (var j = 0, m = groups.length; j < m; j++) { - group = groups[j]; - for (var i = 0, n = group.length; i < n; i++) { - if (node = group[i]) { - subgroups.push(subgroup = selectAll(node)); - subgroup.parentNode = node; - } - } - } - return d3_selection(subgroups); - } - - // TODO select(function)? - groups.select = function(selector) { - return select(function(node) { - return d3_select(selector, node); - }); - }; - - // TODO selectAll(function)? - groups.selectAll = function(selector) { - return selectAll(function(node) { - return d3_selectAll(selector, node); - }); - }; - - // TODO preserve null elements to maintain index? - groups.filter = function(filter) { - var subgroups = [], - subgroup, - group, - node; - for (var j = 0, m = groups.length; j < m; j++) { - group = groups[j]; - subgroups.push(subgroup = []); - subgroup.parentNode = group.parentNode; - for (var i = 0, n = group.length; i < n; i++) { - if ((node = group[i]) && filter.call(node, node.__data__, i)) { - subgroup.push(node); - } - } - } - return d3_selection(subgroups); - }; - - groups.map = function(map) { - var group, - node; - for (var j = 0, m = groups.length; j < m; j++) { - group = groups[j]; - for (var i = 0, n = group.length; i < n; i++) { - if (node = group[i]) node.__data__ = map.call(node, node.__data__, i); - } - } - return groups; - }; - - // TODO data(null) for clearing data? - groups.data = function(data, join) { - var enter = [], - update = [], - exit = []; - - function bind(group, groupData) { - var i = 0, - n = group.length, - m = groupData.length, - n0 = Math.min(n, m), - n1 = Math.max(n, m), - updateNodes = [], - enterNodes = [], - exitNodes = [], - node, - nodeData; - - if (join) { - var nodeByKey = {}, - keys = [], - key, - j = groupData.length; - - for (i = 0; i < n; i++) { - key = join.call(node = group[i], node.__data__, i); - if (key in nodeByKey) { - exitNodes[j++] = node; // duplicate key - } else { - nodeByKey[key] = node; - } - keys.push(key); - } - - for (i = 0; i < m; i++) { - node = nodeByKey[key = join.call(groupData, nodeData = groupData[i], i)]; - if (node) { - node.__data__ = nodeData; - updateNodes[i] = node; - enterNodes[i] = exitNodes[i] = null; - } else { - enterNodes[i] = d3_selection_enterNode(nodeData); - updateNodes[i] = exitNodes[i] = null; - } - delete nodeByKey[key]; - } - - for (i = 0; i < n; i++) { - if (keys[i] in nodeByKey) { - exitNodes[i] = group[i]; - } - } +d3.selection.prototype = d3_selectionPrototype; +d3_selectionPrototype.select = function(selector) { + var subgroups = [], + subgroup, + subnode, + group, + node; + + if (typeof selector !== "function") selector = d3_selection_selector(selector); + + for (var j = -1, m = this.length; ++j < m;) { + subgroups.push(subgroup = []); + subgroup.parentNode = (group = this[j]).parentNode; + for (var i = -1, n = group.length; ++i < n;) { + if (node = group[i]) { + subgroup.push(subnode = selector.call(node, node.__data__, i)); + if (subnode && "__data__" in node) subnode.__data__ = node.__data__; } else { - for (; i < n0; i++) { - node = group[i]; - nodeData = groupData[i]; - if (node) { - node.__data__ = nodeData; - updateNodes[i] = node; - enterNodes[i] = exitNodes[i] = null; - } else { - enterNodes[i] = d3_selection_enterNode(nodeData); - updateNodes[i] = exitNodes[i] = null; - } - } - for (; i < m; i++) { - enterNodes[i] = d3_selection_enterNode(groupData[i]); - updateNodes[i] = exitNodes[i] = null; - } - for (; i < n1; i++) { - exitNodes[i] = group[i]; - enterNodes[i] = updateNodes[i] = null; - } - } - - enterNodes.parentNode - = updateNodes.parentNode - = exitNodes.parentNode - = group.parentNode; - - enter.push(enterNodes); - update.push(updateNodes); - exit.push(exitNodes); - } - - var i = -1, - n = groups.length, - group; - if (typeof data === "function") { - while (++i < n) { - bind(group = groups[i], data.call(group, group.parentNode.__data__, i)); - } - } else { - while (++i < n) { - bind(group = groups[i], data); + subgroup.push(null); } } + }
- var selection = d3_selection(update); - selection.enter = function() { - return d3_selectionEnter(enter); - }; - selection.exit = function() { - return d3_selection(exit); - }; - return selection; - }; + return d3_selection(subgroups); +};
- // TODO mask forEach? or rename for eachData? - // TODO offer the same semantics for map, reduce, etc.? - groups.each = function(callback) { - for (var j = 0, m = groups.length; j < m; j++) { - var group = groups[j]; - for (var i = 0, n = group.length; i < n; i++) { - var node = group[i]; - if (node) callback.call(node, node.__data__, i); - } - } - return groups; +function d3_selection_selector(selector) { + return function() { + return d3_select(selector, this); }; - - function first(callback) { - for (var j = 0, m = groups.length; j < m; j++) { - var group = groups[j]; - for (var i = 0, n = group.length; i < n; i++) { - var node = group[i]; - if (node) return callback.call(node, node.__data__, i); +} +d3_selectionPrototype.selectAll = function(selector) { + var subgroups = [], + subgroup, + node; + + if (typeof selector !== "function") selector = d3_selection_selectorAll(selector); + + for (var j = -1, m = this.length; ++j < m;) { + for (var group = this[j], i = -1, n = group.length; ++i < n;) { + if (node = group[i]) { + subgroups.push(subgroup = d3_array(selector.call(node, node.__data__, i))); + subgroup.parentNode = node; } } - return null; }
- groups.empty = function() { - return !first(function() { return true; }); - }; + return d3_selection(subgroups); +};
- groups.node = function() { - return first(function() { return this; }); +function d3_selection_selectorAll(selector) { + return function() { + return d3_selectAll(selector, this); }; +} +d3_selectionPrototype.attr = function(name, value) { + name = d3.ns.qualify(name); + + // If no value is specified, return the first value. + if (arguments.length < 2) { + var node = this.node(); + return name.local + ? node.getAttributeNS(name.space, name.local) + : node.getAttribute(name); + }
- groups.attr = function(name, value) { - name = d3.ns.qualify(name); - - // If no value is specified, return the first value. - if (arguments.length < 2) { - return first(name.local - ? function() { return this.getAttributeNS(name.space, name.local); } - : function() { return this.getAttribute(name); }); - } - - /** @this {Element} */ - function attrNull() { - this.removeAttribute(name); - } + function attrNull() { + this.removeAttribute(name); + }
- /** @this {Element} */ - function attrNullNS() { - this.removeAttributeNS(name.space, name.local); - } + function attrNullNS() { + this.removeAttributeNS(name.space, name.local); + }
- /** @this {Element} */ - function attrConstant() { - this.setAttribute(name, value); - } + function attrConstant() { + this.setAttribute(name, value); + }
- /** @this {Element} */ - function attrConstantNS() { - this.setAttributeNS(name.space, name.local, value); - } + function attrConstantNS() { + this.setAttributeNS(name.space, name.local, value); + }
- /** @this {Element} */ - function attrFunction() { - var x = value.apply(this, arguments); - if (x == null) this.removeAttribute(name); - else this.setAttribute(name, x); - } + function attrFunction() { + var x = value.apply(this, arguments); + if (x == null) this.removeAttribute(name); + else this.setAttribute(name, x); + }
- /** @this {Element} */ - function attrFunctionNS() { - var x = value.apply(this, arguments); - if (x == null) this.removeAttributeNS(name.space, name.local); - else this.setAttributeNS(name.space, name.local, x); - } + function attrFunctionNS() { + var x = value.apply(this, arguments); + if (x == null) this.removeAttributeNS(name.space, name.local); + else this.setAttributeNS(name.space, name.local, x); + }
- return groups.each(value == null - ? (name.local ? attrNullNS : attrNull) : (typeof value === "function" - ? (name.local ? attrFunctionNS : attrFunction) - : (name.local ? attrConstantNS : attrConstant))); - }; + return this.each(value == null + ? (name.local ? attrNullNS : attrNull) : (typeof value === "function" + ? (name.local ? attrFunctionNS : attrFunction) + : (name.local ? attrConstantNS : attrConstant))); +}; +d3_selectionPrototype.classed = function(name, value) { + var names = name.split(d3_selection_classedWhitespace), + n = names.length, + i = -1; + if (arguments.length > 1) { + while (++i < n) d3_selection_classed.call(this, names[i], value); + return this; + } else { + while (++i < n) if (!d3_selection_classed.call(this, names[i])) return false; + return true; + } +};
- groups.classed = function(name, value) { - var re = new RegExp("(^|\s+)" + d3.requote(name) + "(\s+|$)", "g"); +var d3_selection_classedWhitespace = /\s+/g;
- // If no value is specified, return the first value. - if (arguments.length < 2) { - return first(function() { - if (c = this.classList) return c.contains(name); - var c = this.className; - re.lastIndex = 0; - return re.test(c.baseVal != null ? c.baseVal : c); - }); - } +function d3_selection_classed(name, value) { + var re = new RegExp("(^|\s+)" + d3.requote(name) + "(\s+|$)", "g");
- /** @this {Element} */ - function classedAdd() { - if (c = this.classList) return c.add(name); - var c = this.className, - cb = c.baseVal != null, - cv = cb ? c.baseVal : c; - re.lastIndex = 0; - if (!re.test(cv)) { - cv = d3_collapse(cv + " " + name); - if (cb) c.baseVal = cv; - else this.className = cv; - } - } + // If no value is specified, return the first value. + if (arguments.length < 2) { + var node = this.node(); + if (c = node.classList) return c.contains(name); + var c = node.className; + re.lastIndex = 0; + return re.test(c.baseVal != null ? c.baseVal : c); + }
- /** @this {Element} */ - function classedRemove() { - if (c = this.classList) return c.remove(name); - var c = this.className, - cb = c.baseVal != null, - cv = cb ? c.baseVal : c; - cv = d3_collapse(cv.replace(re, " ")); + function classedAdd() { + if (c = this.classList) return c.add(name); + var c = this.className, + cb = c.baseVal != null, + cv = cb ? c.baseVal : c; + re.lastIndex = 0; + if (!re.test(cv)) { + cv = d3_collapse(cv + " " + name); if (cb) c.baseVal = cv; else this.className = cv; } + }
- /** @this {Element} */ - function classedFunction() { - (value.apply(this, arguments) - ? classedAdd - : classedRemove).call(this); - } + function classedRemove() { + if (c = this.classList) return c.remove(name); + var c = this.className, + cb = c.baseVal != null, + cv = cb ? c.baseVal : c; + cv = d3_collapse(cv.replace(re, " ")); + if (cb) c.baseVal = cv; + else this.className = cv; + }
- return groups.each(typeof value === "function" - ? classedFunction : value + function classedFunction() { + (value.apply(this, arguments) ? classedAdd - : classedRemove); - }; - - groups.style = function(name, value, priority) { - if (arguments.length < 3) priority = ""; + : classedRemove).call(this); + }
- // If no value is specified, return the first value. - if (arguments.length < 2) { - return first(function() { - return window.getComputedStyle(this, null).getPropertyValue(name); - }); - } + return this.each(typeof value === "function" + ? classedFunction : value + ? classedAdd + : classedRemove); +} +d3_selectionPrototype.style = function(name, value, priority) { + if (arguments.length < 3) priority = "";
- /** @this {Element} */ - function styleNull() { - this.style.removeProperty(name); - } + // If no value is specified, return the first value. + if (arguments.length < 2) return window + .getComputedStyle(this.node(), null) + .getPropertyValue(name);
- /** @this {Element} */ - function styleConstant() { - this.style.setProperty(name, value, priority); - } + function styleNull() { + this.style.removeProperty(name); + }
- /** @this {Element} */ - function styleFunction() { - var x = value.apply(this, arguments); - if (x == null) this.style.removeProperty(name); - else this.style.setProperty(name, x, priority); - } + function styleConstant() { + this.style.setProperty(name, value, priority); + }
- return groups.each(value == null - ? styleNull : (typeof value === "function" - ? styleFunction : styleConstant)); - }; + function styleFunction() { + var x = value.apply(this, arguments); + if (x == null) this.style.removeProperty(name); + else this.style.setProperty(name, x, priority); + }
- groups.property = function(name, value) { - name = d3.ns.qualify(name); + return this.each(value == null + ? styleNull : (typeof value === "function" + ? styleFunction : styleConstant)); +}; +d3_selectionPrototype.property = function(name, value) {
- // If no value is specified, return the first value. - if (arguments.length < 2) { - return first(function() { - return this[name]; - }); - } + // If no value is specified, return the first value. + if (arguments.length < 2) return this.node()[name];
- /** @this {Element} */ - function propertyNull() { - delete this[name]; - } + function propertyNull() { + delete this[name]; + }
- /** @this {Element} */ - function propertyConstant() { - this[name] = value; - } + function propertyConstant() { + this[name] = value; + }
- /** @this {Element} */ - function propertyFunction() { - var x = value.apply(this, arguments); - if (x == null) delete this[name]; - else this[name] = x; - } + function propertyFunction() { + var x = value.apply(this, arguments); + if (x == null) delete this[name]; + else this[name] = x; + }
- return groups.each(value == null - ? propertyNull : (typeof value === "function" - ? propertyFunction : propertyConstant)); - }; + return this.each(value == null + ? propertyNull : (typeof value === "function" + ? propertyFunction : propertyConstant)); +}; +d3_selectionPrototype.text = function(value) { + return arguments.length < 1 + ? this.node().textContent : this.each(typeof value === "function" + ? function() { var v = value.apply(this, arguments); this.textContent = v == null ? "" : v; } : value == null + ? function() { this.textContent = ""; } + : function() { this.textContent = value; }); +}; +d3_selectionPrototype.html = function(value) { + return arguments.length < 1 + ? this.node().innerHTML : this.each(typeof value === "function" + ? function() { var v = value.apply(this, arguments); this.innerHTML = v == null ? "" : v; } : value == null + ? function() { this.innerHTML = ""; } + : function() { this.innerHTML = value; }); +}; +// TODO append(node)? +// TODO append(function)? +d3_selectionPrototype.append = function(name) { + name = d3.ns.qualify(name);
- groups.text = function(value) { + function append() { + return this.appendChild(document.createElementNS(this.namespaceURI, name)); + }
- // If no value is specified, return the first value. - if (arguments.length < 1) { - return first(function() { - return this.textContent; - }); - } + function appendNS() { + return this.appendChild(document.createElementNS(name.space, name.local)); + }
- /** @this {Element} */ - function textConstant() { - this.textContent = value; - } + return this.select(name.local ? appendNS : append); +}; +// TODO insert(node, function)? +// TODO insert(function, string)? +// TODO insert(function, function)? +d3_selectionPrototype.insert = function(name, before) { + name = d3.ns.qualify(name); + + function insert() { + return this.insertBefore( + document.createElementNS(this.namespaceURI, name), + d3_select(before, this)); + }
- /** @this {Element} */ - function textFunction() { - this.textContent = value.apply(this, arguments); - } + function insertNS() { + return this.insertBefore( + document.createElementNS(name.space, name.local), + d3_select(before, this)); + }
- return groups.each(typeof value === "function" - ? textFunction : textConstant); - }; + return this.select(name.local ? insertNS : insert); +}; +// TODO remove(selector)? +// TODO remove(node)? +// TODO remove(function)? +d3_selectionPrototype.remove = function() { + return this.each(function() { + var parent = this.parentNode; + if (parent) parent.removeChild(this); + }); +}; +// TODO data(null) for clearing data? +d3_selectionPrototype.data = function(data, join) { + var enter = [], + update = [], + exit = []; + + function bind(group, groupData) { + var i, + n = group.length, + m = groupData.length, + n0 = Math.min(n, m), + n1 = Math.max(n, m), + updateNodes = [], + enterNodes = [], + exitNodes = [], + node, + nodeData; + + if (join) { + var nodeByKey = {}, + keys = [], + key, + j = groupData.length; + + for (i = -1; ++i < n;) { + key = join.call(node = group[i], node.__data__, i); + if (key in nodeByKey) { + exitNodes[j++] = node; // duplicate key + } else { + nodeByKey[key] = node; + } + keys.push(key); + }
- groups.html = function(value) { + for (i = -1; ++i < m;) { + node = nodeByKey[key = join.call(groupData, nodeData = groupData[i], i)]; + if (node) { + node.__data__ = nodeData; + updateNodes[i] = node; + enterNodes[i] = exitNodes[i] = null; + } else { + enterNodes[i] = d3_selection_dataNode(nodeData); + updateNodes[i] = exitNodes[i] = null; + } + delete nodeByKey[key]; + }
- // If no value is specified, return the first value. - if (arguments.length < 1) { - return first(function() { - return this.innerHTML; - }); - } - - /** @this {Element} */ - function htmlConstant() { - this.innerHTML = value; + for (i = -1; ++i < n;) { + if (keys[i] in nodeByKey) { + exitNodes[i] = group[i]; + } + } + } else { + for (i = -1; ++i < n0;) { + node = group[i]; + nodeData = groupData[i]; + if (node) { + node.__data__ = nodeData; + updateNodes[i] = node; + enterNodes[i] = exitNodes[i] = null; + } else { + enterNodes[i] = d3_selection_dataNode(nodeData); + updateNodes[i] = exitNodes[i] = null; + } + } + for (; i < m; ++i) { + enterNodes[i] = d3_selection_dataNode(groupData[i]); + updateNodes[i] = exitNodes[i] = null; + } + for (; i < n1; ++i) { + exitNodes[i] = group[i]; + enterNodes[i] = updateNodes[i] = null; + } }
- /** @this {Element} */ - function htmlFunction() { - this.innerHTML = value.apply(this, arguments); - } + enterNodes.update + = updateNodes;
- return groups.each(typeof value === "function" - ? htmlFunction : htmlConstant); - }; + enterNodes.parentNode + = updateNodes.parentNode + = exitNodes.parentNode + = group.parentNode;
- // TODO append(node)? - // TODO append(function)? - groups.append = function(name) { - name = d3.ns.qualify(name); + enter.push(enterNodes); + update.push(updateNodes); + exit.push(exitNodes); + }
- function append(node) { - return node.appendChild(document.createElement(name)); + var i = -1, + n = this.length, + group; + if (typeof data === "function") { + while (++i < n) { + bind(group = this[i], data.call(group, group.parentNode.__data__, i)); } - - function appendNS(node) { - return node.appendChild(document.createElementNS(name.space, name.local)); + } else { + while (++i < n) { + bind(group = this[i], data); } + }
- return select(name.local ? appendNS : append); - }; - - // TODO insert(node, function)? - // TODO insert(function, string)? - // TODO insert(function, function)? - groups.insert = function(name, before) { - name = d3.ns.qualify(name); + var selection = d3_selection(update); + selection.enter = function() { return d3_selection_enter(enter); }; + selection.exit = function() { return d3_selection(exit); }; + return selection; +};
- function insert(node) { - return node.insertBefore( - document.createElement(name), - d3_select(before, node)); +function d3_selection_dataNode(data) { + return {__data__: data}; +} +d3_selectionPrototype.filter = function(filter) { + var subgroups = [], + subgroup, + group, + node; + + if (typeof filter !== "function") filter = d3_selection_filter(filter); + + for (var j = 0, m = this.length; j < m; j++) { + subgroups.push(subgroup = []); + subgroup.parentNode = (group = this[j]).parentNode; + for (var i = 0, n = group.length; i < n; i++) { + if ((node = group[i]) && filter.call(node, node.__data__, i)) { + subgroup.push(node); + } } + }
- function insertNS(node) { - return node.insertBefore( - document.createElementNS(name.space, name.local), - d3_select(before, node)); - } + return d3_selection(subgroups); +};
- return select(name.local ? insertNS : insert); +function d3_selection_filter(selector) { + return function() { + return d3_selectMatches(this, selector); }; +} +d3_selectionPrototype.map = function(map) { + return this.each(function() { + this.__data__ = map.apply(this, arguments); + }); +}; +d3_selectionPrototype.order = function() { + for (var j = -1, m = this.length; ++j < m;) { + for (var group = this[j], i = group.length - 1, next = group[i], node; --i >= 0;) { + if (node = group[i]) { + if (next) next.parentNode.insertBefore(node, next); + next = node; + } + } + } + return this; +}; +d3_selectionPrototype.sort = function(comparator) { + comparator = d3_selection_sortComparator.apply(this, arguments); + for (var j = -1, m = this.length; ++j < m;) this[j].sort(comparator); + return this.order(); +};
- // TODO remove(selector)? - // TODO remove(node)? - // TODO remove(function)? - groups.remove = function() { - return groups.each(function() { - var parent = this.parentNode; - if (parent) parent.removeChild(this); - }); +function d3_selection_sortComparator(comparator) { + if (!arguments.length) comparator = d3.ascending; + return function(a, b) { + return comparator(a && a.__data__, b && b.__data__); }; - - groups.sort = function(comparator) { - comparator = d3_selection_comparator.apply(this, arguments); - for (var j = 0, m = groups.length; j < m; j++) { - var group = groups[j]; - group.sort(comparator); - for (var i = 1, n = group.length, prev = group[0]; i < n; i++) { - var node = group[i]; - if (node) { - if (prev) prev.parentNode.insertBefore(node, prev.nextSibling); - prev = node; - } +} +// type can be namespaced, e.g., "click.foo" +// listener can be null for removal +d3_selectionPrototype.on = function(type, listener, capture) { + if (arguments.length < 3) capture = false; + + // parse the type specifier + var name = "__on" + type, i = type.indexOf("."); + if (i > 0) type = type.substring(0, i); + + // if called with only one argument, return the current listener + if (arguments.length < 2) return (i = this.node()[name]) && i._; + + // remove the old event listener, and add the new event listener + return this.each(function(d, i) { + var node = this; + + if (node[name]) node.removeEventListener(type, node[name], capture); + if (listener) node.addEventListener(type, node[name] = l, capture); + + // wrapped event listener that preserves i + function l(e) { + var o = d3.event; // Events can be reentrant (e.g., focus). + d3.event = e; + try { + listener.call(node, node.__data__, i); + } finally { + d3.event = o; } } - return groups; - }; - - // type can be namespaced, e.g., "click.foo" - // listener can be null for removal - groups.on = function(type, listener, capture) { - if (arguments.length < 3) capture = false; - - // parse the type specifier - var i = type.indexOf("."), - typo = i === -1 ? type : type.substring(0, i), - name = "__on" + type;
- // remove the old event listener, and add the new event listener - return groups.each(function(d, i) { - if (this[name]) this.removeEventListener(typo, this[name], capture); - if (listener) this.addEventListener(typo, this[name] = l, capture); - - // wrapped event listener that preserves i - var node = this; - function l(e) { - var o = d3.event; // Events can be reentrant (e.g., focus). - d3.event = e; - try { - listener.call(this, node.__data__, i); - } finally { - d3.event = o; - } - } - }); - }; + // stash the unwrapped listener for retrieval + l._ = listener; + }); +}; +d3_selectionPrototype.each = function(callback) { + for (var j = -1, m = this.length; ++j < m;) { + for (var group = this[j], i = -1, n = group.length; ++i < n;) { + var node = group[i]; + if (node) callback.call(node, node.__data__, i, j); + } + } + return this; +}; +// +// Note: assigning to the arguments array simultaneously changes the value of +// the corresponding argument! +// +// TODO The `this` argument probably shouldn't be the first argument to the +// callback, anyway, since it's redundant. However, that will require a major +// version bump due to backwards compatibility, so I'm not changing it right +// away. +// +d3_selectionPrototype.call = function(callback) { + callback.apply(this, (arguments[0] = this, arguments)); + return this; +}; +d3_selectionPrototype.empty = function() { + return !this.node(); +}; +d3_selectionPrototype.node = function(callback) { + for (var j = 0, m = this.length; j < m; j++) { + for (var group = this[j], i = 0, n = group.length; i < n; i++) { + var node = group[i]; + if (node) return node; + } + } + return null; +}; +d3_selectionPrototype.transition = function() { + var subgroups = [], + subgroup, + node; + + for (var j = -1, m = this.length; ++j < m;) { + subgroups.push(subgroup = []); + for (var group = this[j], i = -1, n = group.length; ++i < n;) { + subgroup.push((node = group[i]) ? {node: node, delay: 0, duration: 250} : null); + } + }
- // TODO slice? + return d3_transition(subgroups, d3_transitionInheritId || ++d3_transitionId, Date.now()); +}; +var d3_selectionRoot = d3_selection([[document]]);
- groups.transition = function() { - return d3_transition(groups); - }; +d3_selectionRoot[0].parentNode = d3_selectRoot;
- groups.call = d3_call; +// TODO fast singleton implementation! +// TODO select(function) +d3.select = function(selector) { + return typeof selector === "string" + ? d3_selectionRoot.select(selector) + : d3_selection([[selector]]); // assume node +};
- return groups; +// TODO selectAll(function) +d3.selectAll = function(selector) { + return typeof selector === "string" + ? d3_selectionRoot.selectAll(selector) + : d3_selection([d3_array(selector)]); // assume node[] +}; +function d3_selection_enter(selection) { + d3_arraySubclass(selection, d3_selection_enterPrototype); + return selection; }
-function d3_selectionEnter(groups) { - - function select(select) { - var subgroups = [], - subgroup, - subnode, - group, - node; - for (var j = 0, m = groups.length; j < m; j++) { - group = groups[j]; - subgroups.push(subgroup = []); - subgroup.parentNode = group.parentNode; - for (var i = 0, n = group.length; i < n; i++) { - if (node = group[i]) { - subgroup.push(subnode = select(group.parentNode)); - subnode.__data__ = node.__data__; - } else { - subgroup.push(null); - } +var d3_selection_enterPrototype = []; + +d3_selection_enterPrototype.append = d3_selectionPrototype.append; +d3_selection_enterPrototype.insert = d3_selectionPrototype.insert; +d3_selection_enterPrototype.empty = d3_selectionPrototype.empty; +d3_selection_enterPrototype.node = d3_selectionPrototype.node; +d3_selection_enterPrototype.select = function(selector) { + var subgroups = [], + subgroup, + subnode, + upgroup, + group, + node; + + for (var j = -1, m = this.length; ++j < m;) { + upgroup = (group = this[j]).update; + subgroups.push(subgroup = []); + subgroup.parentNode = group.parentNode; + for (var i = -1, n = group.length; ++i < n;) { + if (node = group[i]) { + subgroup.push(upgroup[i] = subnode = selector.call(group.parentNode, node.__data__, i)); + subnode.__data__ = node.__data__; + } else { + subgroup.push(null); } } - return d3_selection(subgroups); }
- // TODO append(node)? - // TODO append(function)? - groups.append = function(name) { - name = d3.ns.qualify(name); - - function append(node) { - return node.appendChild(document.createElement(name)); - } - - function appendNS(node) { - return node.appendChild(document.createElementNS(name.space, name.local)); - } - - return select(name.local ? appendNS : append); - }; + return d3_selection(subgroups); +}; +function d3_transition(groups, id, time) { + d3_arraySubclass(groups, d3_transitionPrototype);
- // TODO insert(node, function)? - // TODO insert(function, string)? - // TODO insert(function, function)? - groups.insert = function(name, before) { - name = d3.ns.qualify(name); + var tweens = {}, + event = d3.dispatch("start", "end"), + ease = d3_transitionEase;
- function insert(node) { - return node.insertBefore( - document.createElement(name), - d3_select(before, node)); - } + groups.id = id;
- function insertNS(node) { - return node.insertBefore( - document.createElementNS(name.space, name.local), - d3_select(before, node)); - } + groups.time = time;
- return select(name.local ? insertNS : insert); + groups.tween = function(name, tween) { + if (arguments.length < 2) return tweens[name]; + if (tween == null) delete tweens[name]; + else tweens[name] = tween; + return groups; };
- return groups; -} + groups.ease = function(value) { + if (!arguments.length) return ease; + ease = typeof value === "function" ? value : d3.ease.apply(d3, arguments); + return groups; + };
-function d3_selection_comparator(comparator) { - if (!arguments.length) comparator = d3.ascending; - return function(a, b) { - return comparator(a && a.__data__, b && b.__data__); + groups.each = function(type, listener) { + if (arguments.length < 2) return d3_transition_each.call(groups, type); + event.on(type, listener); + return groups; }; -}
-function d3_selection_enterNode(data) { - return {__data__: data}; -} -d3.transition = d3_root.transition; + d3.timer(function(elapsed) { + groups.each(function(d, i, j) { + var tweened = [], + node = this, + delay = groups[j][i].delay, + duration = groups[j][i].duration, + lock = node.__transition__ || (node.__transition__ = {active: 0, count: 0});
-var d3_transitionId = 0, - d3_transitionInheritId = 0; + ++lock.count;
-function d3_transition(groups) { - var transition = {}, - transitionId = d3_transitionInheritId || ++d3_transitionId, - tweens = {}, - interpolators = [], - remove = false, - event = d3.dispatch("start", "end"), - stage = [], - delay = [], - duration = [], - durationMax, - ease = d3.ease("cubic-in-out"); - - // - // Be careful with concurrent transitions! - // - // Say transition A causes an exit. Before A finishes, a transition B is - // created, and believes it only needs to do an update, because the elements - // haven't been removed yet (which happens at the very end of the exit - // transition). - // - // Even worse, what if either transition A or B has a staggered delay? Then, - // some elements may be removed, while others remain. Transition B does not - // know to enter the elements because they were still present at the time - // the transition B was created (but not yet started). - // - // To prevent such confusion, we only trigger end events for transitions if - // the transition ending is the only one scheduled for the given element. - // Similarly, we only allow one transition to be active for any given - // element, so that concurrent transitions do not overwrite each other's - // properties. - // - // TODO Support transition namespaces, so that transitions can proceed - // concurrently on the same element if needed. Hopefully, this is rare! - // - - groups.each(function() { - (this.__transition__ || (this.__transition__ = {})).owner = transitionId; - }); + delay <= elapsed ? start(elapsed) : d3.timer(start, delay, time);
- function step(elapsed) { - var clear = true, - k = -1; - groups.each(function() { - if (stage[++k] === 2) return; // ended - var t = (elapsed - delay[k]) / duration[k], - tx = this.__transition__, - te, // ease(t) - tk, // tween key - ik = interpolators[k]; - - // Check if the (un-eased) time is outside the range [0,1]. - if (t < 1) { - clear = false; - if (t < 0) return; - } else { - t = 1; - } + function start(elapsed) { + if (lock.active > id) return stop(); + lock.active = id;
- // Determine the stage of this transition. - // 0 - Not yet started. - // 1 - In progress. - // 2 - Ended. - if (stage[k]) { - if (!tx || tx.active !== transitionId) { - stage[k] = 2; - return; - } - } else if (!tx || tx.active > transitionId) { - stage[k] = 2; - return; - } else { - stage[k] = 1; - event.start.dispatch.apply(this, arguments); - ik = interpolators[k] = {}; - tx.active = transitionId; - for (tk in tweens) { - if (te = tweens[tk].apply(this, arguments)) { - ik[tk] = te; + for (var tween in tweens) { + if (tween = tweens[tween].call(node, d, i)) { + tweened.push(tween); } } + + event.start.call(node, d, i); + if (!tick(elapsed)) d3.timer(tick, 0, time); + return 1; }
- // Apply the interpolators! - te = ease(t); - for (tk in ik) ik[tk].call(this, te); - - // Handle ending transitions. - if (t === 1) { - stage[k] = 2; - if (tx.active === transitionId) { - var owner = tx.owner; - if (owner === transitionId) { - delete this.__transition__; - if (remove && this.parentNode) this.parentNode.removeChild(this); - } - d3_transitionInheritId = transitionId; - event.end.dispatch.apply(this, arguments); + function tick(elapsed) { + if (lock.active !== id) return stop(); + + var t = (elapsed - delay) / duration, + e = ease(t), + n = tweened.length; + + while (n > 0) { + tweened[--n].call(node, e); + } + + if (t >= 1) { + stop(); + d3_transitionInheritId = id; + event.end.call(node, d, i); d3_transitionInheritId = 0; - tx.owner = owner; + return 1; } } - }); - return clear; - }
- transition.delay = function(value) { - var delayMin = Infinity, - k = -1; - if (typeof value === "function") { - groups.each(function(d, i) { - var x = delay[++k] = +value.apply(this, arguments); - if (x < delayMin) delayMin = x; - }); - } else { - delayMin = +value; - groups.each(function(d, i) { - delay[++k] = delayMin; - }); - } - d3_timer(step, delayMin); - return transition; - }; - - transition.duration = function(value) { - var k = -1; - if (typeof value === "function") { - durationMax = 0; - groups.each(function(d, i) { - var x = duration[++k] = +value.apply(this, arguments); - if (x > durationMax) durationMax = x; - }); - } else { - durationMax = +value; - groups.each(function(d, i) { - duration[++k] = durationMax; - }); - } - return transition; - }; + function stop() { + if (!--lock.count) delete node.__transition__; + return 1; + } + }); + return 1; + }, 0, time);
- transition.ease = function(value) { - ease = typeof value === "function" ? value : d3.ease.apply(d3, arguments); - return transition; - }; + return groups; +}
- transition.attrTween = function(name, tween) { +var d3_transitionRemove = {};
- /** @this {Element} */ - function attrTween(d, i) { - var f = tween.call(this, d, i, this.getAttribute(name)); - return f && function(t) { - this.setAttribute(name, f(t)); - }; - } +function d3_transitionNull(d, i, a) { + return a != "" && d3_transitionRemove; +}
- /** @this {Element} */ - function attrTweenNS(d, i) { - var f = tween.call(this, d, i, this.getAttributeNS(name.space, name.local)); - return f && function(t) { - this.setAttributeNS(name.space, name.local, f(t)); - }; - } +function d3_transitionTween(name, b) { + var interpolate = d3_interpolateByName(name);
- tweens["attr." + name] = name.local ? attrTweenNS : attrTween; - return transition; - }; + function transitionFunction(d, i, a) { + var v = b.call(this, d, i); + return v == null + ? a != "" && d3_transitionRemove + : a != v && interpolate(a, v); + }
- transition.attr = function(name, value) { - return transition.attrTween(name, d3_transitionTween(value)); - }; + function transitionString(d, i, a) { + return a != b && interpolate(a, b); + }
- transition.styleTween = function(name, tween, priority) { - if (arguments.length < 3) priority = null; + return typeof b === "function" ? transitionFunction + : b == null ? d3_transitionNull + : (b += "", transitionString); +}
- /** @this {Element} */ - function styleTween(d, i) { - var f = tween.call(this, d, i, window.getComputedStyle(this, null).getPropertyValue(name)); - return f && function(t) { - this.style.setProperty(name, f(t), priority); - }; - } +var d3_transitionPrototype = [], + d3_transitionId = 0, + d3_transitionInheritId = 0, + d3_transitionEase = d3.ease("cubic-in-out");
- tweens["style." + name] = styleTween; - return transition; - }; +d3_transitionPrototype.call = d3_selectionPrototype.call;
- transition.style = function(name, value, priority) { - if (arguments.length < 3) priority = null; - return transition.styleTween(name, d3_transitionTween(value), priority); - }; +d3.transition = function() { + return d3_selectionRoot.transition(); +};
- transition.text = function(value) { - tweens.text = function(d, i) { - this.textContent = typeof value === "function" - ? value.call(this, d, i) - : value; - }; - return transition; - }; +d3.transition.prototype = d3_transitionPrototype; +d3_transitionPrototype.select = function(selector) { + var subgroups = [], + subgroup, + subnode, + node; + + if (typeof selector !== "function") selector = d3_selection_selector(selector); + + for (var j = -1, m = this.length; ++j < m;) { + subgroups.push(subgroup = []); + for (var group = this[j], i = -1, n = group.length; ++i < n;) { + if ((node = group[i]) && (subnode = selector.call(node.node, node.node.__data__, i))) { + if ("__data__" in node.node) subnode.__data__ = node.node.__data__; + subgroup.push({node: subnode, delay: node.delay, duration: node.duration}); + } else { + subgroup.push(null); + } + } + }
- transition.select = function(query) { - var k, t = d3_transition(groups.select(query)).ease(ease); - k = -1; t.delay(function(d, i) { return delay[++k]; }); - k = -1; t.duration(function(d, i) { return duration[++k]; }); - return t; - }; + return d3_transition(subgroups, this.id, this.time).ease(this.ease()); +}; +d3_transitionPrototype.selectAll = function(selector) { + var subgroups = [], + subgroup, + subnodes, + node; + + if (typeof selector !== "function") selector = d3_selection_selectorAll(selector); + + for (var j = -1, m = this.length; ++j < m;) { + for (var group = this[j], i = -1, n = group.length; ++i < n;) { + if (node = group[i]) { + subnodes = selector.call(node.node, node.node.__data__, i); + subgroups.push(subgroup = []); + for (var k = -1, o = subnodes.length; ++k < o;) { + subgroup.push({node: subnodes[k], delay: node.delay, duration: node.duration}); + } + } + } + }
- transition.selectAll = function(query) { - var k, t = d3_transition(groups.selectAll(query)).ease(ease); - k = -1; t.delay(function(d, i) { return delay[i ? k : ++k]; }) - k = -1; t.duration(function(d, i) { return duration[i ? k : ++k]; }); - return t; - }; + return d3_transition(subgroups, this.id, this.time).ease(this.ease()); +}; +d3_transitionPrototype.attr = function(name, value) { + return this.attrTween(name, d3_transitionTween(name, value)); +};
- transition.remove = function() { - remove = true; - return transition; - }; +d3_transitionPrototype.attrTween = function(nameNS, tween) { + var name = d3.ns.qualify(nameNS);
- transition.each = function(type, listener) { - event[type].add(listener); - return transition; - }; + function attrTween(d, i) { + var f = tween.call(this, d, i, this.getAttribute(name)); + return f === d3_transitionRemove + ? (this.removeAttribute(name), null) + : f && function(t) { this.setAttribute(name, f(t)); }; + }
- transition.call = d3_call; + function attrTweenNS(d, i) { + var f = tween.call(this, d, i, this.getAttributeNS(name.space, name.local)); + return f === d3_transitionRemove + ? (this.removeAttributeNS(name.space, name.local), null) + : f && function(t) { this.setAttributeNS(name.space, name.local, f(t)); }; + }
- return transition.delay(0).duration(250); -} + return this.tween("attr." + nameNS, name.local ? attrTweenNS : attrTween); +}; +d3_transitionPrototype.style = function(name, value, priority) { + if (arguments.length < 3) priority = ""; + return this.styleTween(name, d3_transitionTween(name, value), priority); +};
-function d3_transitionTween(b) { - return typeof b === "function" - ? function(d, i, a) { var v = b.call(this, d, i) + ""; return a != v && d3.interpolate(a, v); } - : (b = b + "", function(d, i, a) { return a != b && d3.interpolate(a, b); }); +d3_transitionPrototype.styleTween = function(name, tween, priority) { + if (arguments.length < 3) priority = ""; + return this.tween("style." + name, function(d, i) { + var f = tween.call(this, d, i, window.getComputedStyle(this, null).getPropertyValue(name)); + return f === d3_transitionRemove + ? (this.style.removeProperty(name), null) + : f && function(t) { this.style.setProperty(name, f(t), priority); }; + }); +}; +d3_transitionPrototype.text = function(value) { + return this.tween("text", function(d, i) { + this.textContent = typeof value === "function" + ? value.call(this, d, i) + : value; + }); +}; +d3_transitionPrototype.remove = function() { + return this.each("end", function() { + var p; + if (!this.__transition__ && (p = this.parentNode)) p.removeChild(this); + }); +}; +d3_transitionPrototype.delay = function(value) { + var groups = this; + return groups.each(typeof value === "function" + ? function(d, i, j) { groups[j][i].delay = +value.apply(this, arguments); } + : (value = +value, function(d, i, j) { groups[j][i].delay = value; })); +}; +d3_transitionPrototype.duration = function(value) { + var groups = this; + return groups.each(typeof value === "function" + ? function(d, i, j) { groups[j][i].duration = +value.apply(this, arguments); } + : (value = +value, function(d, i, j) { groups[j][i].duration = value; })); +}; +function d3_transition_each(callback) { + for (var j = 0, m = this.length; j < m; j++) { + for (var group = this[j], i = 0, n = group.length; i < n; i++) { + var node = group[i]; + if (node) callback.call(node = node.node, node.__data__, i, j); + } + } + return this; } +d3_transitionPrototype.transition = function() { + return this.select(d3_this); +}; var d3_timer_queue = null, d3_timer_interval, // is an interval (or frame) active? d3_timer_timeout; // is a timeout active?
// The timer will continue to fire until callback returns true. -d3.timer = function(callback) { - d3_timer(callback, 0); -}; - -function d3_timer(callback, delay) { - var now = Date.now(), - found = false, +d3.timer = function(callback, delay, then) { + var found = false, t0, t1 = d3_timer_queue;
- if (!isFinite(delay)) return; + if (arguments.length < 3) { + if (arguments.length < 2) delay = 0; + else if (!isFinite(delay)) return; + then = Date.now(); + }
// See if the callback's already in the queue. while (t1) { if (t1.callback === callback) { - t1.then = now; + t1.then = then; t1.delay = delay; found = true; break; @@ -2105,7 +2205,7 @@ function d3_timer(callback, delay) { // Otherwise, add the callback to the queue. if (!found) d3_timer_queue = { callback: callback, - then: now, + then: then, delay: delay, next: d3_timer_queue }; @@ -2125,7 +2225,7 @@ function d3_timer_step() {
while (t1) { elapsed = now - t1.then; - if (elapsed > t1.delay) t1.flush = t1.callback(elapsed); + if (elapsed >= t1.delay) t1.flush = t1.callback(elapsed); t1 = t1.next; }
@@ -2178,6 +2278,66 @@ var d3_timer_frame = window.requestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(callback) { setTimeout(callback, 17); }; +d3.transform = function(string) { + var g = document.createElementNS(d3.ns.prefix.svg, "g"), + identity = {a: 1, b: 0, c: 0, d: 1, e: 0, f: 0}; + return (d3.transform = function(string) { + g.setAttribute("transform", string); + var t = g.transform.baseVal.consolidate(); + return new d3_transform(t ? t.matrix : identity); + })(string); +}; + +// Compute x-scale and normalize the first row. +// Compute shear and make second row orthogonal to first. +// Compute y-scale and normalize the second row. +// Finally, compute the rotation. +function d3_transform(m) { + var r0 = [m.a, m.b], + r1 = [m.c, m.d], + kx = d3_transformNormalize(r0), + kz = d3_transformDot(r0, r1), + ky = d3_transformNormalize(d3_transformCombine(r1, r0, -kz)) || 0; + if (r0[0] * r1[1] < r1[0] * r0[1]) { + r0[0] *= -1; + r0[1] *= -1; + kx *= -1; + kz *= -1; + } + this.rotate = (kx ? Math.atan2(r0[1], r0[0]) : Math.atan2(-r1[0], r1[1])) * d3_transformDegrees; + this.translate = [m.e, m.f]; + this.scale = [kx, ky]; + this.skew = ky ? Math.atan2(kz, ky) * d3_transformDegrees : 0; +}; + +d3_transform.prototype.toString = function() { + return "translate(" + this.translate + + ")rotate(" + this.rotate + + ")skewX(" + this.skew + + ")scale(" + this.scale + + ")"; +}; + +function d3_transformDot(a, b) { + return a[0] * b[0] + a[1] * b[1]; +} + +function d3_transformNormalize(a) { + var k = Math.sqrt(d3_transformDot(a, a)); + if (k) { + a[0] /= k; + a[1] /= k; + } + return k; +} + +function d3_transformCombine(a, b, k) { + a[0] += k * b[0]; + a[1] += k * b[1]; + return a; +} + +var d3_transformDegrees = 180 / Math.PI; function d3_noop() {} d3.scale = {};
@@ -2185,6 +2345,10 @@ function d3_scaleExtent(domain) { var start = domain[0], stop = domain[domain.length - 1]; return start < stop ? [start, stop] : [stop, start]; } + +function d3_scaleRange(scale) { + return scale.rangeExtent ? scale.rangeExtent() : d3_scaleExtent(scale.range()); +} function d3_scale_nice(domain, nice) { var i0 = 0, i1 = domain.length - 1, @@ -2197,9 +2361,12 @@ function d3_scale_nice(domain, nice) { dx = x0; x0 = x1; x1 = dx; }
- nice = nice(x1 - x0); - domain[i0] = nice.floor(x0); - domain[i1] = nice.ceil(x1); + if (dx = x1 - x0) { + nice = nice(dx); + domain[i0] = nice.floor(x0); + domain[i1] = nice.ceil(x1); + } + return domain; }
@@ -2207,11 +2374,11 @@ function d3_scale_niceDefault() { return Math; } d3.scale.linear = function() { - var domain = [0, 1], - range = [0, 1], - interpolate = d3.interpolate, - clamp = false, - output, + return d3_scale_linear([0, 1], [0, 1], d3.interpolate, false); +}; + +function d3_scale_linear(domain, range, interpolate, clamp) { + var output, input;
function rescale() { @@ -2272,15 +2439,15 @@ d3.scale.linear = function() { return rescale(); };
+ scale.copy = function() { + return d3_scale_linear(domain, range, interpolate, clamp); + }; + return rescale(); };
function d3_scale_linearRebind(scale, linear) { - scale.range = d3.rebind(scale, linear.range); - scale.rangeRound = d3.rebind(scale, linear.rangeRound); - scale.interpolate = d3.rebind(scale, linear.interpolate); - scale.clamp = d3.rebind(scale, linear.clamp); - return scale; + return d3.rebind(scale, linear, "range", "rangeRound", "interpolate", "clamp"); }
function d3_scale_linearNice(dx) { @@ -2341,9 +2508,11 @@ function d3_scale_polylinear(domain, range, uninterpolate, interpolate) { }; } d3.scale.log = function() { - var linear = d3.scale.linear(), - log = d3_scale_log, - pow = log.pow; + return d3_scale_log(d3.scale.linear(), d3_scale_logp); +}; + +function d3_scale_log(linear, log) { + var pow = log.pow;
function scale(x) { return linear(log(x)); @@ -2355,7 +2524,7 @@ d3.scale.log = function() {
scale.domain = function(x) { if (!arguments.length) return linear.domain().map(pow); - log = x[0] < 0 ? d3_scale_logn : d3_scale_log; + log = x[0] < 0 ? d3_scale_logn : d3_scale_logp; pow = log.pow; linear.domain(x.map(log)); return scale; @@ -2388,14 +2557,27 @@ d3.scale.log = function() { return ticks; };
- scale.tickFormat = function() { - return d3_scale_logTickFormat; + scale.tickFormat = function(n, format) { + if (arguments.length < 2) format = d3_scale_logFormat; + if (arguments.length < 1) return format; + var k = n / scale.ticks().length, + f = log === d3_scale_logn ? (e = -1e-12, Math.floor) : (e = 1e-12, Math.ceil), + e; + return function(d) { + return d / pow(f(log(d) + e)) < k ? format(d) : ""; + }; + }; + + scale.copy = function() { + return d3_scale_log(linear.copy(), log); };
return d3_scale_linearRebind(scale, linear); };
-function d3_scale_log(x) { +var d3_scale_logFormat = d3.format(".0e"); + +function d3_scale_logp(x) { return Math.log(x) / Math.LN10; }
@@ -2403,22 +2585,20 @@ function d3_scale_logn(x) { return -Math.log(-x) / Math.LN10; }
-d3_scale_log.pow = function(x) { +d3_scale_logp.pow = function(x) { return Math.pow(10, x); };
d3_scale_logn.pow = function(x) { return -Math.pow(10, -x); }; - -function d3_scale_logTickFormat(d) { - return d.toPrecision(1); -} d3.scale.pow = function() { - var linear = d3.scale.linear(), - exponent = 1, - powp = Number, - powb = powp; + return d3_scale_pow(d3.scale.linear(), 1); +}; + +function d3_scale_pow(linear, exponent) { + var powp = d3_scale_powPow(exponent), + powb = d3_scale_powPow(1 / exponent);
function scale(x) { return linear(powp(x)); @@ -2430,8 +2610,6 @@ d3.scale.pow = function() {
scale.domain = function(x) { if (!arguments.length) return linear.domain().map(powb); - powp = d3_scale_powPow(exponent); - powb = d3_scale_powPow(1 / exponent); linear.domain(x.map(powp)); return scale; }; @@ -2451,10 +2629,15 @@ d3.scale.pow = function() { scale.exponent = function(x) { if (!arguments.length) return exponent; var domain = scale.domain(); - exponent = x; + powp = d3_scale_powPow(exponent = x); + powb = d3_scale_powPow(1 / exponent); return scale.domain(domain); };
+ scale.copy = function() { + return d3_scale_pow(linear.copy(), exponent); + }; + return d3_scale_linearRebind(scale, linear); };
@@ -2467,73 +2650,69 @@ d3.scale.sqrt = function() { return d3.scale.pow().exponent(.5); }; d3.scale.ordinal = function() { - var domain = [], - index = {}, - range = [], - rangeBand = 0, - rerange = d3_noop; + return d3_scale_ordinal([], {t: "range", x: []}); +}; + +function d3_scale_ordinal(domain, ranger) { + var index, + range, + rangeBand;
function scale(x) { - var i = x in index ? index[x] : (index[x] = domain.push(x) - 1); - return range[i % range.length]; + return range[((index[x] || (index[x] = domain.push(x))) - 1) % range.length]; + } + + function steps(start, step) { + return d3.range(domain.length).map(function(i) { return start + step * i; }); }
scale.domain = function(x) { if (!arguments.length) return domain; - domain = x; + domain = []; index = {}; - var i = -1, j = -1, n = domain.length; while (++i < n) { - x = domain[i]; - if (!(x in index)) index[x] = ++j; - } - rerange(); - return scale; + var i = -1, n = x.length, xi; + while (++i < n) if (!index[xi = x[i]]) index[xi] = domain.push(xi); + return scale[ranger.t](ranger.x, ranger.p); };
scale.range = function(x) { if (!arguments.length) return range; range = x; - rerange = d3_noop; + rangeBand = 0; + ranger = {t: "range", x: x}; return scale; };
scale.rangePoints = function(x, padding) { if (arguments.length < 2) padding = 0; - (rerange = function() { - var start = x[0], - stop = x[1], - step = (stop - start) / (domain.length - 1 + padding); - range = domain.length == 1 - ? [(start + stop) / 2] - : d3.range(start + step * padding / 2, stop + step / 2, step); - rangeBand = 0; - })(); + var start = x[0], + stop = x[1], + step = (stop - start) / (domain.length - 1 + padding); + range = steps(domain.length < 2 ? (start + stop) / 2 : start + step * padding / 2, step); + rangeBand = 0; + ranger = {t: "rangePoints", x: x, p: padding}; return scale; };
scale.rangeBands = function(x, padding) { if (arguments.length < 2) padding = 0; - (rerange = function() { - var start = x[0], - stop = x[1], - step = (stop - start) / (domain.length + padding); - range = d3.range(start + step * padding, stop, step); - rangeBand = step * (1 - padding); - })(); + var start = x[0], + stop = x[1], + step = (stop - start) / (domain.length + padding); + range = steps(start + step * padding, step); + rangeBand = step * (1 - padding); + ranger = {t: "rangeBands", x: x, p: padding}; return scale; };
scale.rangeRoundBands = function(x, padding) { if (arguments.length < 2) padding = 0; - (rerange = function() { - var start = x[0], - stop = x[1], - diff = stop - start, - step = Math.floor(diff / (domain.length + padding)), - err = diff - (domain.length - padding) * step; - range = d3.range(start + Math.round(err / 2), stop, step); - rangeBand = Math.round(step * (1 - padding)); - })(); + var start = x[0], + stop = x[1], + step = Math.floor((stop - start) / (domain.length + padding)); + range = steps(start + Math.round((stop - start - (domain.length - padding) * step) / 2), step); + rangeBand = Math.round(step * (1 - padding)); + ranger = {t: "rangeRoundBands", x: x, p: padding}; return scale; };
@@ -2541,7 +2720,15 @@ d3.scale.ordinal = function() { return rangeBand; };
- return scale; + scale.rangeExtent = function() { + return ranger.t === "range" ? d3_scaleExtent(ranger.x) : ranger.x; + }; + + scale.copy = function() { + return d3_scale_ordinal(domain, ranger); + }; + + return scale.domain(domain); }; /* * This product includes color specifications and designs developed by Cynthia @@ -2598,16 +2785,19 @@ var d3_category20c = [ "#636363", "#969696", "#bdbdbd", "#d9d9d9" ]; d3.scale.quantile = function() { - var domain = [], - range = [], - thresholds = []; + return d3_scale_quantile([], []); +}; + +function d3_scale_quantile(domain, range) { + var thresholds;
function rescale() { var k = 0, n = domain.length, q = range.length; - thresholds.length = Math.max(0, q - 1); + thresholds = []; while (++k < q) thresholds[k - 1] = d3.quantile(domain, k / q); + return scale; }
function scale(x) { @@ -2618,51 +2808,60 @@ d3.scale.quantile = function() { scale.domain = function(x) { if (!arguments.length) return domain; domain = x.filter(function(d) { return !isNaN(d); }).sort(d3.ascending); - rescale(); - return scale; + return rescale(); };
scale.range = function(x) { if (!arguments.length) return range; range = x; - rescale(); - return scale; + return rescale(); };
scale.quantiles = function() { return thresholds; };
- return scale; + scale.copy = function() { + return d3_scale_quantile(domain, range); // copy on write! + }; + + return rescale(); }; d3.scale.quantize = function() { - var x0 = 0, - x1 = 1, - kx = 2, - i = 1, - range = [0, 1]; + return d3_scale_quantize(0, 1, [0, 1]); +}; + +function d3_scale_quantize(x0, x1, range) { + var kx, i;
function scale(x) { return range[Math.max(0, Math.min(i, Math.floor(kx * (x - x0))))]; }
- scale.domain = function(x) { - if (!arguments.length) return [x0, x1]; - x0 = x[0]; - x1 = x[1]; + function rescale() { kx = range.length / (x1 - x0); + i = range.length - 1; return scale; + } + + scale.domain = function(x) { + if (!arguments.length) return [x0, x1]; + x0 = +x[0]; + x1 = +x[x.length - 1]; + return rescale(); };
scale.range = function(x) { if (!arguments.length) return range; range = x; - kx = range.length / (x1 - x0); - i = range.length - 1; - return scale; + return rescale(); + }; + + scale.copy = function() { + return d3_scale_quantize(x0, x1, range); // copy on write };
- return scale; + return rescale(); }; d3.svg = {}; d3.svg.arc = function() { @@ -2676,7 +2875,7 @@ d3.svg.arc = function() { r1 = outerRadius.apply(this, arguments), a0 = startAngle.apply(this, arguments) + d3_svg_arcOffset, a1 = endAngle.apply(this, arguments) + d3_svg_arcOffset, - da = a1 - a0, + da = (a1 < a0 && (da = a0, a0 = a1, a1 = da), a1 - a0), df = da < Math.PI ? "0" : "1", c0 = Math.cos(a0), s0 = Math.sin(a0), @@ -2688,8 +2887,8 @@ d3.svg.arc = function() { + "A" + r1 + "," + r1 + " 0 1,1 0," + (-r1) + "A" + r1 + "," + r1 + " 0 1,1 0," + r1 + "M0," + r0 - + "A" + r0 + "," + r0 + " 0 1,1 0," + (-r0) - + "A" + r0 + "," + r0 + " 0 1,1 0," + r0 + + "A" + r0 + "," + r0 + " 0 1,0 0," + (-r0) + + "A" + r0 + "," + r0 + " 0 1,0 0," + r0 + "Z" : "M0," + r1 + "A" + r1 + "," + r1 + " 0 1,1 0," + (-r1) @@ -2855,33 +3054,30 @@ var d3_svg_lineInterpolators = {
// Linear interpolation; generates "L" commands. function d3_svg_lineLinear(points) { - var path = [], - i = 0, + var i = 0, n = points.length, - p = points[0]; - path.push(p[0], ",", p[1]); + p = points[0], + path = [p[0], ",", p[1]]; while (++i < n) path.push("L", (p = points[i])[0], ",", p[1]); return path.join(""); }
// Step interpolation; generates "H" and "V" commands. function d3_svg_lineStepBefore(points) { - var path = [], - i = 0, + var i = 0, n = points.length, - p = points[0]; - path.push(p[0], ",", p[1]); + p = points[0], + path = [p[0], ",", p[1]]; while (++i < n) path.push("V", (p = points[i])[1], "H", p[0]); return path.join(""); }
// Step interpolation; generates "H" and "V" commands. function d3_svg_lineStepAfter(points) { - var path = [], - i = 0, + var i = 0, n = points.length, - p = points[0]; - path.push(p[0], ",", p[1]); + p = points[0], + path = [p[0], ",", p[1]]; while (++i < n) path.push("H", (p = points[i])[0], "V", p[1]); return path.join(""); } @@ -2979,15 +3175,14 @@ function d3_svg_lineCardinalTangents(points, tension) { // B-spline interpolation; generates "C" commands. function d3_svg_lineBasis(points) { if (points.length < 3) return d3_svg_lineLinear(points); - var path = [], - i = 1, + var i = 1, n = points.length, pi = points[0], x0 = pi[0], y0 = pi[1], px = [x0, x0, x0, (pi = points[1])[0]], - py = [y0, y0, y0, pi[1]]; - path.push(x0, ",", y0); + py = [y0, y0, y0, pi[1]], + path = [x0, ",", y0]; d3_svg_lineBasisBezier(path, px, py); while (++i < n) { pi = points[i]; @@ -3206,16 +3401,17 @@ function d3_svg_area(projection) { x1 = d3_svg_lineX, y0 = 0, y1 = d3_svg_lineY, - interpolate = "linear", - interpolator = d3_svg_lineInterpolators[interpolate], + interpolate, + i0, + i1, tension = .7;
function area(d) { if (d.length < 1) return null; var points0 = d3_svg_linePoints(this, d, x0, y0), points1 = d3_svg_linePoints(this, d, x0 === x1 ? d3_svg_areaX(points0) : x1, y0 === y1 ? d3_svg_areaY(points0) : y1); - return "M" + interpolator(projection(points1), tension) - + "L" + interpolator(projection(points0.reverse()), tension) + return "M" + i0(projection(points1), tension) + + "L" + i1(projection(points0.reverse()), tension) + "Z"; }
@@ -3257,7 +3453,8 @@ function d3_svg_area(projection) {
area.interpolate = function(x) { if (!arguments.length) return interpolate; - interpolator = d3_svg_lineInterpolators[interpolate = x]; + i0 = d3_svg_lineInterpolators[interpolate = x]; + i1 = i0.reverse || i0; return area; };
@@ -3267,9 +3464,12 @@ function d3_svg_area(projection) { return area; };
- return area; + return area.interpolate("linear"); }
+d3_svg_lineStepBefore.reverse = d3_svg_lineStepAfter; +d3_svg_lineStepAfter.reverse = d3_svg_lineStepBefore; + d3.svg.area = function() { return d3_svg_area(Object); }; @@ -3308,10 +3508,10 @@ d3.svg.chord = function() { var s = subgroup(this, source, d, i), t = subgroup(this, target, d, i); return "M" + s.p0 - + arc(s.r, s.p1) + (equals(s, t) + + arc(s.r, s.p1, s.a1 - s.a0) + (equals(s, t) ? curve(s.r, s.p1, s.r, s.p0) : curve(s.r, s.p1, t.r, t.p0) - + arc(t.r, t.p1) + + arc(t.r, t.p1, t.a1 - t.a0) + curve(t.r, t.p1, s.r, s.p0)) + "Z"; } @@ -3334,8 +3534,8 @@ d3.svg.chord = function() { return a.a0 == b.a0 && a.a1 == b.a1; }
- function arc(r, p) { - return "A" + r + "," + r + " 0 0,1 " + p; + function arc(r, p, a) { + return "A" + r + "," + r + " 0 " + +(a > Math.PI) + ",1 " + p; }
function curve(r0, p0, r1, p1) { @@ -3465,7 +3665,7 @@ function d3_svg_mousePoint(container, e) { var point = (container.ownerSVGElement || container).createSVGPoint(); if ((d3_mouse_bug44083 < 0) && (window.scrollX || window.scrollY)) { var svg = d3.select(document.body) - .append("svg:svg") + .append("svg") .style("position", "absolute") .style("top", 0) .style("left", 0); @@ -3483,8 +3683,9 @@ function d3_svg_mousePoint(container, e) { point = point.matrixTransform(container.getScreenCTM().inverse()); return [point.x, point.y]; }; -d3.svg.touches = function(container) { - var touches = d3.event.touches; +d3.svg.touches = function(container, touches) { + if (arguments.length < 2) touches = d3.event.touches; + return touches ? d3_array(touches).map(function(touch) { var point = d3_svg_mousePoint(container, touch); point.identifier = touch.identifier; @@ -3589,4 +3790,906 @@ d3.svg.symbolTypes = d3.keys(d3_svg_symbols);
var d3_svg_symbolSqrt3 = Math.sqrt(3), d3_svg_symbolTan30 = Math.tan(30 * Math.PI / 180); +d3.svg.axis = function() { + var scale = d3.scale.linear(), + orient = "bottom", + tickMajorSize = 6, + tickMinorSize = 6, + tickEndSize = 6, + tickPadding = 3, + tickArguments_ = [10], + tickFormat_, + tickSubdivide = 0; + + function axis(selection) { + selection.each(function(d, i, j) { + var g = d3.select(this); + + // If selection is a transition, create subtransitions. + var transition = selection.delay ? function(o) { + var id = d3_transitionInheritId; + try { + d3_transitionInheritId = selection.id; + return o.transition() + .delay(selection[j][i].delay) + .duration(selection[j][i].duration) + .ease(selection.ease()); + } finally { + d3_transitionInheritId = id; + } + } : Object; + + // Ticks, or domain values for ordinal scales. + var ticks = scale.ticks ? scale.ticks.apply(scale, tickArguments_) : scale.domain(), + tickFormat = tickFormat_ == null ? (scale.tickFormat ? scale.tickFormat.apply(scale, tickArguments_) : String) : tickFormat_; + + // Minor ticks. + var subticks = d3_svg_axisSubdivide(scale, ticks, tickSubdivide), + subtick = g.selectAll(".minor").data(subticks, String), + subtickEnter = subtick.enter().insert("line", "g").attr("class", "tick minor").style("opacity", 1e-6), + subtickExit = transition(subtick.exit()).style("opacity", 1e-6).remove(), + subtickUpdate = transition(subtick).style("opacity", 1); + + // Major ticks. + var tick = g.selectAll("g").data(ticks, String), + tickEnter = tick.enter().insert("g", "path").style("opacity", 1e-6), + tickExit = transition(tick.exit()).style("opacity", 1e-6).remove(), + tickUpdate = transition(tick).style("opacity", 1), + tickTransform; + + // Domain. + var range = d3_scaleRange(scale), + path = g.selectAll(".domain").data([0]), + pathEnter = path.enter().append("path").attr("class", "domain"), + pathUpdate = transition(path); + + // Stash a snapshot of the new scale, and retrieve the old snapshot. + var scale1 = scale.copy(), + scale0 = this.__chart__ || scale1; + this.__chart__ = scale1; + + tickEnter.append("line").attr("class", "tick"); + tickEnter.append("text"); + tickUpdate.select("text").text(tickFormat); + + switch (orient) { + case "bottom": { + tickTransform = d3_svg_axisX; + subtickUpdate.attr("x2", 0).attr("y2", tickMinorSize); + tickUpdate.select("line").attr("x2", 0).attr("y2", tickMajorSize); + tickUpdate.select("text").attr("x", 0).attr("y", Math.max(tickMajorSize, 0) + tickPadding).attr("dy", ".71em").attr("text-anchor", "middle"); + pathUpdate.attr("d", "M" + range[0] + "," + tickEndSize + "V0H" + range[1] + "V" + tickEndSize); + break; + } + case "top": { + tickTransform = d3_svg_axisX; + subtickUpdate.attr("x2", 0).attr("y2", -tickMinorSize); + tickUpdate.select("line").attr("x2", 0).attr("y2", -tickMajorSize); + tickUpdate.select("text").attr("x", 0).attr("y", -(Math.max(tickMajorSize, 0) + tickPadding)).attr("dy", "0em").attr("text-anchor", "middle"); + pathUpdate.attr("d", "M" + range[0] + "," + -tickEndSize + "V0H" + range[1] + "V" + -tickEndSize); + break; + } + case "left": { + tickTransform = d3_svg_axisY; + subtickUpdate.attr("x2", -tickMinorSize).attr("y2", 0); + tickUpdate.select("line").attr("x2", -tickMajorSize).attr("y2", 0); + tickUpdate.select("text").attr("x", -(Math.max(tickMajorSize, 0) + tickPadding)).attr("y", 0).attr("dy", ".32em").attr("text-anchor", "end"); + pathUpdate.attr("d", "M" + -tickEndSize + "," + range[0] + "H0V" + range[1] + "H" + -tickEndSize); + break; + } + case "right": { + tickTransform = d3_svg_axisY; + subtickUpdate.attr("x2", tickMinorSize).attr("y2", 0); + tickUpdate.select("line").attr("x2", tickMajorSize).attr("y2", 0); + tickUpdate.select("text").attr("x", Math.max(tickMajorSize, 0) + tickPadding).attr("y", 0).attr("dy", ".32em").attr("text-anchor", "start"); + pathUpdate.attr("d", "M" + tickEndSize + "," + range[0] + "H0V" + range[1] + "H" + tickEndSize); + break; + } + } + + // For quantitative scales: + // - enter new ticks from the old scale + // - exit old ticks to the new scale + if (scale.ticks) { + tickEnter.call(tickTransform, scale0); + tickUpdate.call(tickTransform, scale1); + tickExit.call(tickTransform, scale1); + subtickEnter.call(tickTransform, scale0); + subtickUpdate.call(tickTransform, scale1); + subtickExit.call(tickTransform, scale1); + } + + // For ordinal scales: + // - any entering ticks are undefined in the old scale + // - any exiting ticks are undefined in the new scale + // Therefore, we only need to transition updating ticks. + else { + var dx = scale1.rangeBand() / 2, x = function(d) { return scale1(d) + dx; }; + tickEnter.call(tickTransform, x); + tickUpdate.call(tickTransform, x); + } + }); + } + + axis.scale = function(x) { + if (!arguments.length) return scale; + scale = x; + return axis; + }; + + axis.orient = function(x) { + if (!arguments.length) return orient; + orient = x; + return axis; + }; + + axis.ticks = function() { + if (!arguments.length) return tickArguments_; + tickArguments_ = arguments; + return axis; + }; + + axis.tickFormat = function(x) { + if (!arguments.length) return tickFormat_; + tickFormat_ = x; + return axis; + }; + + axis.tickSize = function(x, y, z) { + if (!arguments.length) return tickMajorSize; + var n = arguments.length - 1; + tickMajorSize = +x; + tickMinorSize = n > 1 ? +y : tickMajorSize; + tickEndSize = n > 0 ? +arguments[n] : tickMajorSize; + return axis; + }; + + axis.tickPadding = function(x) { + if (!arguments.length) return tickPadding; + tickPadding = +x; + return axis; + }; + + axis.tickSubdivide = function(x) { + if (!arguments.length) return tickSubdivide; + tickSubdivide = +x; + return axis; + }; + + return axis; +}; + +function d3_svg_axisX(selection, x) { + selection.attr("transform", function(d) { return "translate(" + x(d) + ",0)"; }); +} + +function d3_svg_axisY(selection, y) { + selection.attr("transform", function(d) { return "translate(0," + y(d) + ")"; }); +} + +function d3_svg_axisSubdivide(scale, ticks, m) { + subticks = []; + if (m && ticks.length > 1) { + var extent = d3_scaleExtent(scale.domain()), + subticks, + i = -1, + n = ticks.length, + d = (ticks[1] - ticks[0]) / ++m, + j, + v; + while (++i < n) { + for (j = m; --j > 0;) { + if ((v = +ticks[i] - j * d) >= extent[0]) { + subticks.push(v); + } + } + } + for (--i, j = 0; ++j < m && (v = +ticks[i] + j * d) < extent[1];) { + subticks.push(v); + } + } + return subticks; +} +d3.svg.brush = function() { + var event = d3.dispatch("brushstart", "brush", "brushend"), + x, // x-scale, optional + y, // y-scale, optional + extent = [[0, 0], [0, 0]]; // [x0, y0], [x1, y1] + + function brush(g) { + var resizes = x && y ? ["n", "e", "s", "w", "nw", "ne", "se", "sw"] + : x ? ["e", "w"] + : y ? ["n", "s"] + : []; + + g.each(function() { + var g = d3.select(this).on("mousedown.brush", down), + bg = g.selectAll(".background").data([,]), + fg = g.selectAll(".extent").data([,]), + tz = g.selectAll(".resize").data(resizes, String), + e; + + // An invisible, mouseable area for starting a new brush. + bg.enter().append("rect") + .attr("class", "background") + .style("visibility", "hidden") + .style("pointer-events", "all") + .style("cursor", "crosshair"); + + // The visible brush extent; style this as you like! + fg.enter().append("rect") + .attr("class", "extent") + .style("cursor", "move"); + + // More invisible rects for resizing the extent. + tz.enter().append("rect") + .attr("class", function(d) { return "resize " + d; }) + .attr("width", 6) + .attr("height", 6) + .style("visibility", "hidden") + .style("pointer-events", brush.empty() ? "none" : "all") + .style("cursor", function(d) { return d3_svg_brushCursor[d]; }); + + // Remove any superfluous resizers. + tz.exit().remove(); + + // Initialize the background to fill the defined range. + // If the range isn't defined, you can post-process. + if (x) { + e = d3_scaleRange(x); + bg.attr("x", e[0]).attr("width", e[1] - e[0]); + d3_svg_brushRedrawX(g, extent); + } + if (y) { + e = d3_scaleRange(y); + bg.attr("y", e[0]).attr("height", e[1] - e[0]); + d3_svg_brushRedrawY(g, extent); + } + }); + } + + function down() { + var target = d3.select(d3.event.target); + + // Store some global state for the duration of the brush gesture. + d3_svg_brush = brush; + d3_svg_brushTarget = this; + d3_svg_brushExtent = extent; + d3_svg_brushOffset = d3.svg.mouse(d3_svg_brushTarget); + + // If the extent was clicked on, drag rather than brush; + // store the offset between the mouse and extent origin instead. + if (d3_svg_brushDrag = target.classed("extent")) { + d3_svg_brushOffset[0] = extent[0][0] - d3_svg_brushOffset[0]; + d3_svg_brushOffset[1] = extent[0][1] - d3_svg_brushOffset[1]; + } + + // If a resizer was clicked on, record which side is to be resized. + // Also, set the offset to the opposite side. + else if (target.classed("resize")) { + d3_svg_brushResize = d3.event.target.__data__; + d3_svg_brushOffset[0] = extent[+/w$/.test(d3_svg_brushResize)][0]; + d3_svg_brushOffset[1] = extent[+/^n/.test(d3_svg_brushResize)][1]; + } + + // If the ALT key is down when starting a brush, the center is at the mouse. + else if (d3.event.altKey) { + d3_svg_brushCenter = d3_svg_brushOffset.slice(); + } + + // Restrict which dimensions are resized. + d3_svg_brushX = !/^(n|s)$/.test(d3_svg_brushResize) && x; + d3_svg_brushY = !/^(e|w)$/.test(d3_svg_brushResize) && y; + + // Notify listeners. + d3_svg_brushDispatch = dispatcher(this, arguments); + d3_svg_brushDispatch("brushstart"); + d3_svg_brushMove(); + d3_eventCancel(); + } + + function dispatcher(that, argumentz) { + return function(type) { + var e = d3.event; + try { + d3.event = {type: type, target: brush}; + event[type].apply(that, argumentz); + } finally { + d3.event = e; + } + }; + } + + brush.x = function(z) { + if (!arguments.length) return x; + x = z; + return brush; + }; + + brush.y = function(z) { + if (!arguments.length) return y; + y = z; + return brush; + }; + + brush.extent = function(z) { + var x0, x1, y0, y1, t; + + // Invert the pixel extent to data-space. + if (!arguments.length) { + if (x) { + x0 = extent[0][0], x1 = extent[1][0]; + if (x.invert) x0 = x.invert(x0), x1 = x.invert(x1); + if (x1 < x0) t = x0, x0 = x1, x1 = t; + } + if (y) { + y0 = extent[0][1], y1 = extent[1][1]; + if (y.invert) y0 = y.invert(y0), y1 = y.invert(y1); + if (y1 < y0) t = y0, y0 = y1, y1 = t; + } + return x && y ? [[x0, y0], [x1, y1]] : x ? [x0, x1] : y && [y0, y1]; + } + + // Scale the data-space extent to pixels. + if (x) { + x0 = z[0], x1 = z[1]; + if (y) x0 = x0[0], x1 = x1[0]; + if (x.invert) x0 = x(x0), x1 = x(x1); + if (x1 < x0) t = x0, x0 = x1, x1 = t; + extent[0][0] = x0, extent[1][0] = x1; + } + if (y) { + y0 = z[0], y1 = z[1]; + if (x) y0 = y0[1], y1 = y1[1]; + if (y.invert) y0 = y(y0), y1 = y(y1); + if (y1 < y0) t = y0, y0 = y1, y1 = t; + extent[0][1] = y0, extent[1][1] = y1; + } + + return brush; + }; + + brush.clear = function() { + extent[0][0] = + extent[0][1] = + extent[1][0] = + extent[1][1] = 0; + return brush; + }; + + brush.empty = function() { + return (x && extent[0][0] === extent[1][0]) + || (y && extent[0][1] === extent[1][1]); + }; + + d3.select(window) + .on("mousemove.brush", d3_svg_brushMove) + .on("mouseup.brush", d3_svg_brushUp) + .on("keydown.brush", d3_svg_brushKeydown) + .on("keyup.brush", d3_svg_brushKeyup); + + return d3.rebind(brush, event, "on"); +}; + +var d3_svg_brush, + d3_svg_brushDispatch, + d3_svg_brushTarget, + d3_svg_brushX, + d3_svg_brushY, + d3_svg_brushExtent, + d3_svg_brushDrag, + d3_svg_brushResize, + d3_svg_brushCenter, + d3_svg_brushOffset; + +function d3_svg_brushRedrawX(g, extent) { + g.select(".extent").attr("x", extent[0][0]); + g.selectAll(".n,.s,.w,.nw,.sw").attr("x", extent[0][0] - 2); + g.selectAll(".e,.ne,.se").attr("x", extent[1][0] - 3); + g.selectAll(".extent,.n,.s").attr("width", extent[1][0] - extent[0][0]); +} + +function d3_svg_brushRedrawY(g, extent) { + g.select(".extent").attr("y", extent[0][1]); + g.selectAll(".n,.e,.w,.nw,.ne").attr("y", extent[0][1] - 3); + g.selectAll(".s,.se,.sw").attr("y", extent[1][1] - 4); + g.selectAll(".extent,.e,.w").attr("height", extent[1][1] - extent[0][1]); +} + +function d3_svg_brushKeydown() { + if (d3.event.keyCode == 32 && d3_svg_brushTarget && !d3_svg_brushDrag) { + d3_svg_brushCenter = null; + d3_svg_brushOffset[0] -= d3_svg_brushExtent[1][0]; + d3_svg_brushOffset[1] -= d3_svg_brushExtent[1][1]; + d3_svg_brushDrag = 2; + d3_eventCancel(); + } +} + +function d3_svg_brushKeyup() { + if (d3.event.keyCode == 32 && d3_svg_brushDrag == 2) { + d3_svg_brushOffset[0] += d3_svg_brushExtent[1][0]; + d3_svg_brushOffset[1] += d3_svg_brushExtent[1][1]; + d3_svg_brushDrag = 0; + d3_eventCancel(); + } +} + +function d3_svg_brushMove() { + if (d3_svg_brushOffset) { + var mouse = d3.svg.mouse(d3_svg_brushTarget), + g = d3.select(d3_svg_brushTarget); + + if (!d3_svg_brushDrag) { + + // If needed, determine the center from the current extent. + if (d3.event.altKey) { + if (!d3_svg_brushCenter) { + d3_svg_brushCenter = [ + (d3_svg_brushExtent[0][0] + d3_svg_brushExtent[1][0]) / 2, + (d3_svg_brushExtent[0][1] + d3_svg_brushExtent[1][1]) / 2 + ]; + } + + // Update the offset, for when the ALT key is released. + d3_svg_brushOffset[0] = d3_svg_brushExtent[+(mouse[0] < d3_svg_brushCenter[0])][0]; + d3_svg_brushOffset[1] = d3_svg_brushExtent[+(mouse[1] < d3_svg_brushCenter[1])][1]; + } + + // When the ALT key is released, we clear the center. + else d3_svg_brushCenter = null; + } + + // Update the brush extent for each dimension. + if (d3_svg_brushX) { + d3_svg_brushMove1(mouse, d3_svg_brushX, 0); + d3_svg_brushRedrawX(g, d3_svg_brushExtent); + } + if (d3_svg_brushY) { + d3_svg_brushMove1(mouse, d3_svg_brushY, 1); + d3_svg_brushRedrawY(g, d3_svg_brushExtent); + } + + // Notify listeners. + d3_svg_brushDispatch("brush"); + } +} + +function d3_svg_brushMove1(mouse, scale, i) { + var range = d3_scaleRange(scale), + r0 = range[0], + r1 = range[1], + offset = d3_svg_brushOffset[i], + size = d3_svg_brushExtent[1][i] - d3_svg_brushExtent[0][i], + min, + max; + + // When dragging, reduce the range by the extent size and offset. + if (d3_svg_brushDrag) { + r0 -= offset; + r1 -= size + offset; + } + + // Clamp the mouse so that the extent fits within the range extent. + min = Math.max(r0, Math.min(r1, mouse[i])); + + // Compute the new extent bounds. + if (d3_svg_brushDrag) { + max = (min += offset) + size; + } else { + + // If the ALT key is pressed, then preserve the center of the extent. + if (d3_svg_brushCenter) offset = Math.max(r0, Math.min(r1, 2 * d3_svg_brushCenter[i] - min)); + + // Compute the min and max of the offset and mouse. + if (offset < min) { + max = min; + min = offset; + } else { + max = offset; + } + } + + // Update the stored bounds. + d3_svg_brushExtent[0][i] = min; + d3_svg_brushExtent[1][i] = max; +} + +function d3_svg_brushUp() { + if (d3_svg_brushOffset) { + d3_svg_brushMove(); + d3.select(d3_svg_brushTarget).selectAll(".resize").style("pointer-events", d3_svg_brush.empty() ? "none" : "all"); + d3_svg_brushDispatch("brushend"); + d3_svg_brush = + d3_svg_brushDispatch = + d3_svg_brushTarget = + d3_svg_brushX = + d3_svg_brushY = + d3_svg_brushExtent = + d3_svg_brushDrag = + d3_svg_brushResize = + d3_svg_brushCenter = + d3_svg_brushOffset = null; + d3_eventCancel(); + } +} + +var d3_svg_brushCursor = { + n: "ns-resize", + e: "ew-resize", + s: "ns-resize", + w: "ew-resize", + nw: "nwse-resize", + ne: "nesw-resize", + se: "nwse-resize", + sw: "nesw-resize" +}; +d3.behavior = {}; +// TODO Track touch points by identifier. + +d3.behavior.drag = function() { + var event = d3.dispatch("drag", "dragstart", "dragend"), + origin = null; + + function drag() { + this + .on("mousedown.drag", mousedown) + .on("touchstart.drag", mousedown); + + d3.select(window) + .on("mousemove.drag", d3_behavior_dragMove) + .on("touchmove.drag", d3_behavior_dragMove) + .on("mouseup.drag", d3_behavior_dragUp, true) + .on("touchend.drag", d3_behavior_dragUp, true) + .on("click.drag", d3_behavior_dragClick, true); + } + + // snapshot the local context for subsequent dispatch + function start() { + d3_behavior_dragEvent = event; + d3_behavior_dragEventTarget = d3.event.target; + d3_behavior_dragTarget = this; + d3_behavior_dragArguments = arguments; + d3_behavior_dragOrigin = d3_behavior_dragPoint(); + if (origin) { + d3_behavior_dragOffset = origin.apply(d3_behavior_dragTarget, d3_behavior_dragArguments); + d3_behavior_dragOffset = [d3_behavior_dragOffset.x - d3_behavior_dragOrigin[0], d3_behavior_dragOffset.y - d3_behavior_dragOrigin[1]]; + } else { + d3_behavior_dragOffset = [0, 0]; + } + d3_behavior_dragMoved = 0; + } + + function mousedown() { + start.apply(this, arguments); + d3_behavior_dragDispatch("dragstart"); + } + + drag.origin = function(x) { + if (!arguments.length) return origin; + origin = x; + return drag; + }; + + return d3.rebind(drag, event, "on"); +}; + +var d3_behavior_dragEvent, + d3_behavior_dragEventTarget, + d3_behavior_dragTarget, + d3_behavior_dragArguments, + d3_behavior_dragOffset, + d3_behavior_dragOrigin, + d3_behavior_dragMoved; + +function d3_behavior_dragDispatch(type) { + var p = d3_behavior_dragPoint(), + o = d3.event, + e = d3.event = {type: type}; + + if (p) { + e.x = p[0] + d3_behavior_dragOffset[0]; + e.y = p[1] + d3_behavior_dragOffset[1]; + e.dx = p[0] - d3_behavior_dragOrigin[0]; + e.dy = p[1] - d3_behavior_dragOrigin[1]; + d3_behavior_dragMoved |= e.dx | e.dy; + d3_behavior_dragOrigin = p; + } + + try { + d3_behavior_dragEvent[type].apply(d3_behavior_dragTarget, d3_behavior_dragArguments); + } finally { + d3.event = o; + } + + o.stopPropagation(); + o.preventDefault(); +} + +function d3_behavior_dragPoint() { + var p = d3_behavior_dragTarget.parentNode, + t = d3.event.changedTouches; + return p && (t + ? d3.svg.touches(p, t)[0] + : d3.svg.mouse(p)); +} + +function d3_behavior_dragMove() { + if (!d3_behavior_dragTarget) return; + var parent = d3_behavior_dragTarget.parentNode; + + // O NOES! The drag element was removed from the DOM. + if (!parent) return d3_behavior_dragUp(); + + d3_behavior_dragDispatch("drag"); + d3_eventCancel(); +} + +function d3_behavior_dragUp() { + if (!d3_behavior_dragTarget) return; + d3_behavior_dragDispatch("dragend"); + + // If the node was moved, prevent the mouseup from propagating. + // Also prevent the subsequent click from propagating (e.g., for anchors). + if (d3_behavior_dragMoved) { + d3_eventCancel(); + d3_behavior_dragMoved = d3.event.target === d3_behavior_dragEventTarget; + } + + d3_behavior_dragEvent = + d3_behavior_dragEventTarget = + d3_behavior_dragTarget = + d3_behavior_dragArguments = + d3_behavior_dragOffset = + d3_behavior_dragOrigin = null; +} + +function d3_behavior_dragClick() { + if (d3_behavior_dragMoved) { + d3_eventCancel(); + d3_behavior_dragMoved = 0; + } +} +// TODO unbind zoom behavior? +d3.behavior.zoom = function() { + var xyz = [0, 0, 0], + event = d3.dispatch("zoom"), + extent = d3_behavior_zoomInfiniteExtent; + + function zoom() { + this + .on("mousedown.zoom", mousedown) + .on("mousewheel.zoom", mousewheel) + .on("DOMMouseScroll.zoom", mousewheel) + .on("dblclick.zoom", dblclick) + .on("touchstart.zoom", touchstart); + + d3.select(window) + .on("mousemove.zoom", d3_behavior_zoomMousemove) + .on("mouseup.zoom", d3_behavior_zoomMouseup) + .on("touchmove.zoom", d3_behavior_zoomTouchmove) + .on("touchend.zoom", d3_behavior_zoomTouchup) + .on("click.zoom", d3_behavior_zoomClick, true); + } + + // snapshot the local context for subsequent dispatch + function start() { + d3_behavior_zoomXyz = xyz; + d3_behavior_zoomExtent = extent; + d3_behavior_zoomDispatch = event.zoom; + d3_behavior_zoomEventTarget = d3.event.target; + d3_behavior_zoomTarget = this; + d3_behavior_zoomArguments = arguments; + } + + function mousedown() { + start.apply(this, arguments); + d3_behavior_zoomPanning = d3_behavior_zoomLocation(d3.svg.mouse(d3_behavior_zoomTarget)); + d3_behavior_zoomMoved = 0; + d3.event.preventDefault(); + window.focus(); + } + + // store starting mouse location + function mousewheel() { + start.apply(this, arguments); + if (!d3_behavior_zoomZooming) d3_behavior_zoomZooming = d3_behavior_zoomLocation(d3.svg.mouse(d3_behavior_zoomTarget)); + d3_behavior_zoomTo(d3_behavior_zoomDelta() + xyz[2], d3.svg.mouse(d3_behavior_zoomTarget), d3_behavior_zoomZooming); + } + + function dblclick() { + start.apply(this, arguments); + var mouse = d3.svg.mouse(d3_behavior_zoomTarget); + d3_behavior_zoomTo(d3.event.shiftKey ? Math.ceil(xyz[2] - 1) : Math.floor(xyz[2] + 1), mouse, d3_behavior_zoomLocation(mouse)); + } + + // doubletap detection + function touchstart() { + start.apply(this, arguments); + var touches = d3_behavior_zoomTouchup(), + touch, + now = Date.now(); + if ((touches.length === 1) && (now - d3_behavior_zoomLast < 300)) { + d3_behavior_zoomTo(1 + Math.floor(xyz[2]), touch = touches[0], d3_behavior_zoomLocations[touch.identifier]); + } + d3_behavior_zoomLast = now; + } + + zoom.extent = function(x) { + if (!arguments.length) return extent; + extent = x == null ? d3_behavior_zoomInfiniteExtent : x; + return zoom; + }; + + return d3.rebind(zoom, event, "on"); +}; + +var d3_behavior_zoomDiv, + d3_behavior_zoomPanning, + d3_behavior_zoomZooming, + d3_behavior_zoomLocations = {}, // identifier -> location + d3_behavior_zoomLast = 0, + d3_behavior_zoomXyz, + d3_behavior_zoomExtent, + d3_behavior_zoomDispatch, + d3_behavior_zoomEventTarget, + d3_behavior_zoomTarget, + d3_behavior_zoomArguments, + d3_behavior_zoomMoved; + +function d3_behavior_zoomLocation(point) { + return [ + point[0] - d3_behavior_zoomXyz[0], + point[1] - d3_behavior_zoomXyz[1], + d3_behavior_zoomXyz[2] + ]; +} + +// detect the pixels that would be scrolled by this wheel event +function d3_behavior_zoomDelta() { + + // mousewheel events are totally broken! + // https://bugs.webkit.org/show_bug.cgi?id=40441 + // not only that, but Chrome and Safari differ in re. to acceleration! + if (!d3_behavior_zoomDiv) { + d3_behavior_zoomDiv = d3.select("body").append("div") + .style("visibility", "hidden") + .style("top", 0) + .style("height", 0) + .style("width", 0) + .style("overflow-y", "scroll") + .append("div") + .style("height", "2000px") + .node().parentNode; + } + + var e = d3.event, delta; + try { + d3_behavior_zoomDiv.scrollTop = 1000; + d3_behavior_zoomDiv.dispatchEvent(e); + delta = 1000 - d3_behavior_zoomDiv.scrollTop; + } catch (error) { + delta = e.wheelDelta || (-e.detail * 5); + } + + return delta * .005; +} + +// Note: Since we don't rotate, it's possible for the touches to become +// slightly detached from their original positions. Thus, we recompute the +// touch points on touchend as well as touchstart! +function d3_behavior_zoomTouchup() { + var touches = d3.svg.touches(d3_behavior_zoomTarget), + i = -1, + n = touches.length, + touch; + while (++i < n) d3_behavior_zoomLocations[(touch = touches[i]).identifier] = d3_behavior_zoomLocation(touch); + return touches; +} + +function d3_behavior_zoomTouchmove() { + var touches = d3.svg.touches(d3_behavior_zoomTarget); + switch (touches.length) { + + // single-touch pan + case 1: { + var touch = touches[0]; + d3_behavior_zoomTo(d3_behavior_zoomXyz[2], touch, d3_behavior_zoomLocations[touch.identifier]); + break; + } + + // double-touch pan + zoom + case 2: { + var p0 = touches[0], + p1 = touches[1], + p2 = [(p0[0] + p1[0]) / 2, (p0[1] + p1[1]) / 2], + l0 = d3_behavior_zoomLocations[p0.identifier], + l1 = d3_behavior_zoomLocations[p1.identifier], + l2 = [(l0[0] + l1[0]) / 2, (l0[1] + l1[1]) / 2, l0[2]]; + d3_behavior_zoomTo(Math.log(d3.event.scale) / Math.LN2 + l0[2], p2, l2); + break; + } + } +} + +function d3_behavior_zoomMousemove() { + d3_behavior_zoomZooming = null; + if (d3_behavior_zoomPanning) { + d3_behavior_zoomMoved = 1; + d3_behavior_zoomTo(d3_behavior_zoomXyz[2], d3.svg.mouse(d3_behavior_zoomTarget), d3_behavior_zoomPanning); + } +} + +function d3_behavior_zoomMouseup() { + if (d3_behavior_zoomPanning) { + if (d3_behavior_zoomMoved) { + d3_eventCancel(); + d3_behavior_zoomMoved = d3_behavior_zoomEventTarget === d3.event.target; + } + + d3_behavior_zoomXyz = + d3_behavior_zoomExtent = + d3_behavior_zoomDispatch = + d3_behavior_zoomEventTarget = + d3_behavior_zoomTarget = + d3_behavior_zoomArguments = + d3_behavior_zoomPanning = null; + } +} + +function d3_behavior_zoomClick() { + if (d3_behavior_zoomMoved) { + d3_eventCancel(); + d3_behavior_zoomMoved = 0; + } +} + +function d3_behavior_zoomTo(z, x0, x1) { + z = d3_behavior_zoomExtentClamp(z, 2); + var j = Math.pow(2, d3_behavior_zoomXyz[2]), + k = Math.pow(2, z), + K = Math.pow(2, (d3_behavior_zoomXyz[2] = z) - x1[2]), + x_ = d3_behavior_zoomXyz[0], + y_ = d3_behavior_zoomXyz[1], + x = d3_behavior_zoomXyz[0] = d3_behavior_zoomExtentClamp((x0[0] - x1[0] * K), 0, k), + y = d3_behavior_zoomXyz[1] = d3_behavior_zoomExtentClamp((x0[1] - x1[1] * K), 1, k), + o = d3.event; // Events can be reentrant (e.g., focus). + + d3.event = { + scale: k, + translate: [x, y], + transform: function(sx, sy) { + if (sx) transform(sx, x_, x); + if (sy) transform(sy, y_, y); + } + }; + + function transform(scale, a, b) { + scale.domain(scale.range().map(function(v) { return scale.invert(((v - b) * j) / k + a); })); + } + + try { + d3_behavior_zoomDispatch.apply(d3_behavior_zoomTarget, d3_behavior_zoomArguments); + } finally { + d3.event = o; + } + + o.preventDefault(); +} + +var d3_behavior_zoomInfiniteExtent = [ + [-Infinity, Infinity], + [-Infinity, Infinity], + [-Infinity, Infinity] +]; + +function d3_behavior_zoomExtentClamp(x, i, k) { + var range = d3_behavior_zoomExtent[i], + r0 = range[0], + r1 = range[1]; + return arguments.length === 3 + ? Math.max(r1 * (r1 === Infinity ? -Infinity : 1 / k - 1), + Math.min(r0 === -Infinity ? Infinity : r0, x / k)) * k + : Math.max(r0, Math.min(r1, x)); +} })(); diff --git a/modules/enterprise/gui/rest-war/src/main/webapp/js/d3.layout.js b/modules/enterprise/gui/rest-war/src/main/webapp/js/d3.layout.js index 76ddee0..24bcc42 100755 --- a/modules/enterprise/gui/rest-war/src/main/webapp/js/d3.layout.js +++ b/modules/enterprise/gui/rest-war/src/main/webapp/js/d3.layout.js @@ -113,16 +113,19 @@ d3.layout.chord = function() { k = (2 * Math.PI - padding * n) / k;
// Compute the start and end angle for each group and subgroup. + // Note: Opera has a bug reordering object literal properties! x = 0, i = -1; while (++i < n) { x0 = x, j = -1; while (++j < n) { var di = groupIndex[i], - dj = subgroupIndex[i][j], - v = matrix[di][dj]; + dj = subgroupIndex[di][j], + v = matrix[di][dj], + a0 = x, + a1 = x += v * k; subgroups[di + "-" + dj] = { index: di, subindex: dj, - startAngle: x, - endAngle: x += v * k, + startAngle: a0, + endAngle: a1, value: v }; } @@ -143,7 +146,7 @@ d3.layout.chord = function() { if (source.value || target.value) { chords.push(source.value < target.value ? {source: target, target: source} - : {source: source, target: target}) + : {source: source, target: target}); } } } @@ -153,7 +156,9 @@ d3.layout.chord = function() {
function resort() { chords.sort(function(a, b) { - return sortChords(a.target.value, b.target.value); + return sortChords( + (a.source.value + a.target.value) / 2, + (b.source.value + b.target.value) / 2); }); }
@@ -209,6 +214,7 @@ d3.layout.force = function() { var force = {}, event = d3.dispatch("tick"), size = [1, 1], + drag, alpha, friction = .9, linkDistance = d3_layout_forceLinkDistance, @@ -220,9 +226,10 @@ d3.layout.force = function() { nodes = [], links = [], distances, - strengths; + strengths, + charges;
- function repulse(node, kc) { + function repulse(node) { return function(quad, x1, y1, x2, y2) { if (quad.point !== node) { var dx = quad.cx - node.x, @@ -231,30 +238,32 @@ d3.layout.force = function() {
/* Barnes-Hut criterion. */ if ((x2 - x1) * dn < theta) { - var k = kc * quad.count * dn * dn; - node.x += dx * k; - node.y += dy * k; + var k = quad.charge * dn * dn; + node.px -= dx * k; + node.py -= dy * k; return true; }
if (quad.point && isFinite(dn)) { - var k = kc * dn * dn; - node.x += dx * k; - node.y += dy * k; + var k = quad.pointCharge * dn * dn; + node.px -= dx * k; + node.py -= dy * k; } } + return !quad.charge; }; }
function tick() { var n = nodes.length, m = links.length, - q = d3.geom.quadtree(nodes), + q, i, // current index o, // current object s, // current source t, // current target l, // current distance + k, // current force x, // x-distance y; // y-distance
@@ -269,30 +278,32 @@ d3.layout.force = function() { l = alpha * strengths[i] * ((l = Math.sqrt(l)) - distances[i]) / l; x *= l; y *= l; - t.x -= x; - t.y -= y; - s.x += x; - s.y += y; + t.x -= x * (k = s.weight / (t.weight + s.weight)); + t.y -= y * k; + s.x += x * (k = 1 - k); + s.y += y * k; } }
// apply gravity forces - var kg = alpha * gravity; - x = size[0] / 2; - y = size[1] / 2; - i = -1; while (++i < n) { - o = nodes[i]; - o.x += (x - o.x) * kg; - o.y += (y - o.y) * kg; + if (k = alpha * gravity) { + x = size[0] / 2; + y = size[1] / 2; + i = -1; if (k) while (++i < n) { + o = nodes[i]; + o.x += (x - o.x) * k; + o.y += (y - o.y) * k; + } }
- // compute quadtree center of mass - d3_layout_forceAccumulate(q); - - // apply charge forces - var kc = alpha * charge; - i = -1; while (++i < n) { - q.visit(repulse(nodes[i], kc)); + // compute quadtree center of mass and apply charge forces + if (charge) { + d3_layout_forceAccumulate(q = d3.geom.quadtree(nodes), alpha, charges); + i = -1; while (++i < n) { + if (!(o = nodes[i]).fixed) { + q.visit(repulse(o)); + } + } }
// position verlet integration @@ -307,17 +318,12 @@ d3.layout.force = function() { } }
- event.tick.dispatch({type: "tick", alpha: alpha}); + event.tick({type: "tick", alpha: alpha});
// simulated annealing, basically return (alpha *= .99) < .005; }
- force.on = function(type, listener) { - event[type].add(listener); - return force; - }; - force.nodes = function(x) { if (!arguments.length) return nodes; nodes = x; @@ -359,7 +365,7 @@ d3.layout.force = function() {
force.charge = function(x) { if (!arguments.length) return charge; - charge = x; + charge = typeof x === "function" ? x : +x; return force; };
@@ -387,6 +393,7 @@ d3.layout.force = function() {
for (i = 0; i < n; ++i) { (o = nodes[i]).index = i; + o.weight = 0; }
distances = []; @@ -397,6 +404,8 @@ d3.layout.force = function() { if (typeof o.target == "number") o.target = nodes[o.target]; distances[i] = linkDistance.call(this, o, i); strengths[i] = linkStrength.call(this, o, i); + ++o.source.weight; + ++o.target.weight; }
for (i = 0; i < n; ++i) { @@ -407,6 +416,17 @@ d3.layout.force = function() { if (isNaN(o.py)) o.py = o.y; }
+ charges = []; + if (typeof charge === "function") { + for (i = 0; i < n; ++i) { + charges[i] = +charge.call(this, nodes[i], i); + } + } else { + for (i = 0; i < n; ++i) { + charges[i] = charge; + } + } + // initialize node position based on first neighbor function position(dimension, size) { var neighbors = neighbor(i), @@ -449,116 +469,52 @@ d3.layout.force = function() {
// use `node.call(force.drag)` to make nodes draggable force.drag = function() { - - this - .on("mouseover.force", d3_layout_forceDragOver) - .on("mouseout.force", d3_layout_forceDragOut) - .on("mousedown.force", dragdown) - .on("touchstart.force", dragdown); - - d3.select(window) - .on("mousemove.force", d3_layout_forceDragMove) - .on("touchmove.force", d3_layout_forceDragMove) - .on("mouseup.force", d3_layout_forceDragUp, true) - .on("touchend.force", d3_layout_forceDragUp, true) - .on("click.force", d3_layout_forceDragClick, true); - - return force; + if (!drag) drag = d3.behavior.drag() + .origin(Object) + .on("dragstart", dragstart) + .on("drag", d3_layout_forceDrag) + .on("dragend", d3_layout_forceDragEnd); + + this.on("mouseover.force", d3_layout_forceDragOver) + .on("mouseout.force", d3_layout_forceDragOut) + .call(drag); };
- function dragdown(d, i) { - var m = d3_layout_forcePoint(this.parentNode); - (d3_layout_forceDragNode = d).fixed = true; - d3_layout_forceDragMoved = false; - d3_layout_forceDragElement = this; + function dragstart(d) { + d3_layout_forceDragOver(d3_layout_forceDragNode = d); d3_layout_forceDragForce = force; - d3_layout_forceDragOffset = [m[0] - d.x, m[1] - d.y]; - d3_layout_forceCancel(); }
- return force; + return d3.rebind(force, event, "on"); };
var d3_layout_forceDragForce, - d3_layout_forceDragNode, - d3_layout_forceDragMoved, - d3_layout_forceDragOffset, - d3_layout_forceStopClick, - d3_layout_forceDragElement; + d3_layout_forceDragNode;
function d3_layout_forceDragOver(d) { - d.fixed = true; + d.fixed |= 2; }
function d3_layout_forceDragOut(d) { - if (d !== d3_layout_forceDragNode) { - d.fixed = false; - } + if (d !== d3_layout_forceDragNode) d.fixed &= 1; }
-function d3_layout_forcePoint(container) { - return d3.event.touches - ? d3.svg.touches(container)[0] - : d3.svg.mouse(container); +function d3_layout_forceDragEnd() { + d3_layout_forceDrag(); + d3_layout_forceDragNode.fixed &= 1; + d3_layout_forceDragForce = d3_layout_forceDragNode = null; }
-function d3_layout_forceDragMove() { - if (!d3_layout_forceDragNode) return; - var parent = d3_layout_forceDragElement.parentNode; - - // O NOES! The drag element was removed from the DOM. - if (!parent) { - d3_layout_forceDragNode.fixed = false; - d3_layout_forceDragOffset = d3_layout_forceDragNode = d3_layout_forceDragElement = null; - return; - } - - var m = d3_layout_forcePoint(parent); - d3_layout_forceDragMoved = true; - d3_layout_forceDragNode.px = m[0] - d3_layout_forceDragOffset[0]; - d3_layout_forceDragNode.py = m[1] - d3_layout_forceDragOffset[1]; - d3_layout_forceCancel(); +function d3_layout_forceDrag() { + d3_layout_forceDragNode.px = d3.event.x; + d3_layout_forceDragNode.py = d3.event.y; d3_layout_forceDragForce.resume(); // restart annealing }
-function d3_layout_forceDragUp() { - if (!d3_layout_forceDragNode) return; - - // If the node was moved, prevent the mouseup from propagating. - // Also prevent the subsequent click from propagating (e.g., for anchors). - if (d3_layout_forceDragMoved) { - d3_layout_forceStopClick = true; - d3_layout_forceCancel(); - } - - // Don't trigger this for touchend. - if (d3.event.type === "mouseup") { - d3_layout_forceDragMove(); - } - - d3_layout_forceDragNode.fixed = false; - d3_layout_forceDragForce = - d3_layout_forceDragOffset = - d3_layout_forceDragNode = - d3_layout_forceDragElement = null; -} - -function d3_layout_forceDragClick() { - if (d3_layout_forceStopClick) { - d3_layout_forceCancel(); - d3_layout_forceStopClick = false; - } -} - -function d3_layout_forceCancel() { - d3.event.stopPropagation(); - d3.event.preventDefault(); -} - -function d3_layout_forceAccumulate(quad) { +function d3_layout_forceAccumulate(quad, alpha, charges) { var cx = 0, cy = 0; - quad.count = 0; + quad.charge = 0; if (!quad.leaf) { var nodes = quad.nodes, n = nodes.length, @@ -567,10 +523,10 @@ function d3_layout_forceAccumulate(quad) { while (++i < n) { c = nodes[i]; if (c == null) continue; - d3_layout_forceAccumulate(c); - quad.count += c.count; - cx += c.count * c.cx; - cy += c.count * c.cy; + d3_layout_forceAccumulate(c, alpha, charges); + quad.charge += c.charge; + cx += c.charge * c.cx; + cy += c.charge * c.cy; } } if (quad.point) { @@ -579,12 +535,13 @@ function d3_layout_forceAccumulate(quad) { quad.point.x += Math.random() - .5; quad.point.y += Math.random() - .5; } - quad.count++; - cx += quad.point.x; - cy += quad.point.y; + var k = alpha * charges[quad.point.index]; + quad.charge += quad.pointCharge = k; + cx += k * quad.point.x; + cy += k * quad.point.y; } - quad.cx = cx / quad.count; - quad.cy = cy / quad.count; + quad.cx = cx / quad.charge; + quad.cy = cy / quad.charge; }
function d3_layout_forceLinkDistance(link) { @@ -604,12 +561,12 @@ d3.layout.partition = function() { node.y = node.depth * dy; node.dx = dx; node.dy = dy; - if (children) { + if (children && (n = children.length)) { var i = -1, - n = children.length, + n, c, d; - dx /= node.value; + dx = node.value ? dx / node.value : 0; while (++i < n) { position(c = children[i], x, d = c.value * dx, dy); x += d; @@ -620,9 +577,9 @@ d3.layout.partition = function() { function depth(node) { var children = node.children, d = 0; - if (children) { + if (children && (n = children.length)) { var i = -1, - n = children.length; + n; while (++i < n) d = Math.max(d, depth(children[i])); } return 1 + d; @@ -644,48 +601,44 @@ d3.layout.partition = function() { }; d3.layout.pie = function() { var value = Number, - sort = null, + sort = d3_layout_pieSortByValue, startAngle = 0, endAngle = 2 * Math.PI;
function pie(data, i) {
+ // Compute the numeric values for each data element. + var values = data.map(function(d, i) { return +value.call(pie, d, i); }); + // Compute the start angle. var a = +(typeof startAngle === "function" ? startAngle.apply(this, arguments) : startAngle);
- // Compute the angular range (end - start). - var k = (typeof endAngle === "function" + // Compute the angular scale factor: from value to radians. + var k = ((typeof endAngle === "function" ? endAngle.apply(this, arguments) - : endAngle) - startAngle; + : endAngle) - startAngle) + / d3.sum(values);
// Optionally sort the data. var index = d3.range(data.length); - if (sort != null) index.sort(function(i, j) { - return sort(data[i], data[j]); - }); - - // Compute the numeric values for each data element. - var values = data.map(value); - - // Convert k into a scale factor from value to angle, using the sum. - k /= values.reduce(function(p, d) { return p + d; }, 0); + if (sort != null) index.sort(sort === d3_layout_pieSortByValue + ? function(i, j) { return values[j] - values[i]; } + : function(i, j) { return sort(data[i], data[j]); });
// Compute the arcs! - var arcs = index.map(function(i) { - return { + // They are stored in the original data's order. + var arcs = []; + index.forEach(function(i) { + arcs[i] = { data: data[i], value: d = values[i], startAngle: a, endAngle: a += d * k }; }); - - // Return the arcs in the original data's order. - return data.map(function(d, i) { - return arcs[index[i]]; - }); + return arcs; }
/** @@ -737,6 +690,8 @@ d3.layout.pie = function() {
return pie; }; + +var d3_layout_pieSortByValue = {}; // data is two-dimensional array of x,y; we populate y0 d3.layout.stack = function() { var values = Object, @@ -1088,9 +1043,9 @@ d3.layout.hierarchy = function() { node = d3_layout_hierarchyInline ? data : {data: data}; node.depth = depth; nodes.push(node); - if (childs) { + if (childs && (n = childs.length)) { var i = -1, - n = childs.length, + n, c = node.children = [], v = 0, j = depth + 1; @@ -1103,7 +1058,7 @@ d3.layout.hierarchy = function() { if (sort) c.sort(sort); if (value) node.value = v; } else if (value) { - node.value = value.call(hierarchy, data, depth); + node.value = +value.call(hierarchy, data, depth) || 0; } return node; } @@ -1112,13 +1067,13 @@ d3.layout.hierarchy = function() { function revalue(node, depth) { var children = node.children, v = 0; - if (children) { + if (children && (n = children.length)) { var i = -1, - n = children.length, + n, j = depth + 1; while (++i < n) v += revalue(children[i], j); } else if (value) { - v = value.call(hierarchy, d3_layout_hierarchyInline ? node : node.data, depth); + v = +value.call(hierarchy, d3_layout_hierarchyInline ? node : node.data, depth) || 0; } if (value) node.value = v; return v; @@ -1159,10 +1114,10 @@ d3.layout.hierarchy = function() {
// A method assignment helper for hierarchy subclasses. function d3_layout_hierarchyRebind(object, hierarchy) { - object.sort = d3.rebind(object, hierarchy.sort); - object.children = d3.rebind(object, hierarchy.children); + d3.rebind(object, hierarchy, "sort", "children", "value"); + + // Add an alias for links, for convenience. object.links = d3_layout_hierarchyLinks; - object.value = d3.rebind(object, hierarchy.value);
// If the new API is used, enabling inlining. object.nodes = function(d) { @@ -1362,7 +1317,7 @@ function d3_layout_packUnlink(node) {
function d3_layout_packTree(node) { var children = node.children; - if (children) { + if (children && children.length) { children.forEach(d3_layout_packTree); node.r = d3_layout_packCircle(children); } else { @@ -1382,19 +1337,22 @@ function d3_layout_packTransform(node, x, y, k) { }
function d3_layout_packPlace(a, b, c) { - var da = b.r + c.r, - db = a.r + c.r, + var db = a.r + c.r, dx = b.x - a.x, - dy = b.y - a.y, - dc = Math.sqrt(dx * dx + dy * dy), - cos = (db * db + dc * dc - da * da) / (2 * db * dc), - theta = Math.acos(cos), - x = cos * db, - h = Math.sin(theta) * db; - dx /= dc; - dy /= dc; - c.x = a.x + x * dx + h * dy; - c.y = a.y + x * dy - h * dx; + dy = b.y - a.y; + if (db && (dx || dy)) { + var da = b.r + c.r, + dc = Math.sqrt(dx * dx + dy * dy), + cos = Math.max(-1, Math.min(1, (db * db + dc * dc - da * da) / (2 * db * dc))), + theta = Math.acos(cos), + x = cos * (db /= dc), + y = Math.sin(theta) * db; + c.x = a.x + x * dx + y * dy; + c.y = a.y + x * dy - y * dx; + } else { + c.x = a.x + db; + c.y = a.y; + } } // Implements a hierarchical layout using the cluster (or dendogram) algorithm. d3.layout.cluster = function() { @@ -1412,9 +1370,10 @@ d3.layout.cluster = function() {
// First walk, computing the initial x & y values. d3_layout_treeVisitAfter(root, function(node) { - if (node.children) { - node.x = d3_layout_clusterX(node.children); - node.y = d3_layout_clusterY(node.children); + var children = node.children; + if (children && children.length) { + node.x = d3_layout_clusterX(children); + node.y = d3_layout_clusterY(children); } else { node.x = previousNode ? x += separation(node, previousNode) : 0; node.y = 0; @@ -1431,7 +1390,7 @@ d3.layout.cluster = function() { // Second walk, normalizing x & y to the desired size. d3_layout_treeVisitAfter(root, function(node) { node.x = (node.x - x0) / (x1 - x0) * size[0]; - node.y = (1 - node.y / root.y) * size[1]; + node.y = (1 - (root.y ? node.y / root.y : 1)) * size[1]; });
return nodes; @@ -1466,12 +1425,12 @@ function d3_layout_clusterX(children) {
function d3_layout_clusterLeft(node) { var children = node.children; - return children ? d3_layout_clusterLeft(children[0]) : node; + return children && children.length ? d3_layout_clusterLeft(children[0]) : node; }
function d3_layout_clusterRight(node) { - var children = node.children; - return children ? d3_layout_clusterRight(children[children.length - 1]) : node; + var children = node.children, n; + return children && (n = children.length) ? d3_layout_clusterRight(children[n - 1]) : node; } // Node-link tree diagram using the Reingold-Tilford "tidy" algorithm d3.layout.tree = function() { @@ -1486,8 +1445,8 @@ d3.layout.tree = function() { function firstWalk(node, previousSibling) { var children = node.children, layout = node._tree; - if (children && children.length) { - var n = children.length, + if (children && (n = children.length)) { + var n, firstChild = children[0], previousChild, ancestor = firstChild, @@ -1517,9 +1476,9 @@ d3.layout.tree = function() { function secondWalk(node, x) { node.x = node._tree.prelim + x; var children = node.children; - if (children) { + if (children && (n = children.length)) { var i = -1, - n = children.length; + n; x += node._tree.mod; while (++i < n) { secondWalk(children[i], x); @@ -1624,18 +1583,21 @@ function d3_layout_treeSeparation(a, b) { // }
function d3_layout_treeLeft(node) { - return node.children ? node.children[0] : node._tree.thread; + var children = node.children; + return children && children.length ? children[0] : node._tree.thread; }
function d3_layout_treeRight(node) { - return node.children ? node.children[node.children.length - 1] : node._tree.thread; + var children = node.children, + n; + return children && (n = children.length) ? children[n - 1] : node._tree.thread; }
function d3_layout_treeSearch(node, compare) { var children = node.children; - if (children) { + if (children && (n = children.length)) { var child, - n = children.length, + n, i = -1; while (++i < n) { if (compare(child = d3_layout_treeSearch(children[i], compare), node) > 0) { @@ -1661,11 +1623,11 @@ function d3_layout_treeDeepest(a, b) { function d3_layout_treeVisitAfter(node, callback) { function visit(node, previousSibling) { var children = node.children; - if (children) { + if (children && (n = children.length)) { var child, previousChild = null, i = -1, - n = children.length; + n; while (++i < n) { child = children[i]; visit(child, previousChild); @@ -1733,57 +1695,61 @@ d3.layout.treemap = function() {
// Recursively arranges the specified node's children into squarified rows. function squarify(node) { - if (!node.children) return; - var rect = pad(node), - row = [], - children = node.children.slice(), // copy-on-write - child, - best = Infinity, // the best row score so far - score, // the current row score - u = Math.min(rect.dx, rect.dy), // initial orientation - n; - scale(children, rect.dx * rect.dy / node.value); - row.area = 0; - while ((n = children.length) > 0) { - row.push(child = children[n - 1]); - row.area += child.area; - if ((score = worst(row, u)) <= best) { // continue with this orientation - children.pop(); - best = score; - } else { // abort, and try a different orientation - row.area -= row.pop().area; - position(row, u, rect, false); - u = Math.min(rect.dx, rect.dy); + var children = node.children; + if (children && children.length) { + var rect = pad(node), + row = [], + remaining = children.slice(), // copy-on-write + child, + best = Infinity, // the best row score so far + score, // the current row score + u = Math.min(rect.dx, rect.dy), // initial orientation + n; + scale(remaining, rect.dx * rect.dy / node.value); + row.area = 0; + while ((n = remaining.length) > 0) { + row.push(child = remaining[n - 1]); + row.area += child.area; + if ((score = worst(row, u)) <= best) { // continue with this orientation + remaining.pop(); + best = score; + } else { // abort, and try a different orientation + row.area -= row.pop().area; + position(row, u, rect, false); + u = Math.min(rect.dx, rect.dy); + row.length = row.area = 0; + best = Infinity; + } + } + if (row.length) { + position(row, u, rect, true); row.length = row.area = 0; - best = Infinity; } + children.forEach(squarify); } - if (row.length) { - position(row, u, rect, true); - row.length = row.area = 0; - } - node.children.forEach(squarify); }
// Recursively resizes the specified node's children into existing rows. // Preserves the existing layout! function stickify(node) { - if (!node.children) return; - var rect = pad(node), - children = node.children.slice(), // copy-on-write - child, - row = []; - scale(children, rect.dx * rect.dy / node.value); - row.area = 0; - while (child = children.pop()) { - row.push(child); - row.area += child.area; - if (child.z != null) { - position(row, child.z ? rect.dx : rect.dy, rect, !children.length); - row.length = row.area = 0; + var children = node.children; + if (children && children.length) { + var rect = pad(node), + remaining = children.slice(), // copy-on-write + child, + row = []; + scale(remaining, rect.dx * rect.dy / node.value); + row.area = 0; + while (child = remaining.pop()) { + row.push(child); + row.area += child.area; + if (child.z != null) { + position(row, child.z ? rect.dx : rect.dy, rect, !remaining.length); + row.length = row.area = 0; + } } + children.forEach(stickify); } - node.children.forEach(stickify); }
// Computes the score for the specified row, as the worst aspect ratio. @@ -1815,7 +1781,7 @@ d3.layout.treemap = function() { v = u ? round(row.area / u) : 0, o; if (u == rect.dx) { // horizontal subdivision - if (flush || v > rect.dy) v = rect.dy; // over+underflow + if (flush || v > rect.dy) v = v ? rect.dy : 0; // over+underflow while (++i < n) { o = row[i]; o.x = x; @@ -1828,7 +1794,7 @@ d3.layout.treemap = function() { rect.y += v; rect.dy -= v; } else { // vertical subdivision - if (flush || v > rect.dx) v = rect.dx; // over+underflow + if (flush || v > rect.dx) v = v ? rect.dx : 0; // over+underflow while (++i < n) { o = row[i]; o.x = x; diff --git a/modules/enterprise/gui/rest-war/src/main/webapp/js/d3.time.js b/modules/enterprise/gui/rest-war/src/main/webapp/js/d3.time.js index 3a3409f..4c1cda4 100755 --- a/modules/enterprise/gui/rest-war/src/main/webapp/js/d3.time.js +++ b/modules/enterprise/gui/rest-war/src/main/webapp/js/d3.time.js @@ -78,6 +78,7 @@ var d3_time_formats = { H: function(d) { return d3_time_zfill2(d.getHours()); }, I: function(d) { return d3_time_zfill2(d.getHours() % 12 || 12); }, j: d3_time_dayOfYear, + L: function(d) { return d3_time_zfill3(d.getMilliseconds()); }, m: function(d) { return d3_time_zfill2(d.getMonth() + 1); }, M: function(d) { return d3_time_zfill2(d.getMinutes()); }, p: function(d) { return d.getHours() >= 12 ? "PM" : "AM"; }, @@ -104,6 +105,7 @@ var d3_time_parsers = { H: d3_time_parseHour24, I: d3_time_parseHour12, // j: function(d, s, i) { /*TODO day of year [001,366] */ return i; }, + L: d3_time_parseMilliseconds, m: d3_time_parseMonthNumber, M: d3_time_parseMinutes, p: d3_time_parseAmPm, @@ -277,6 +279,12 @@ function d3_time_parseSeconds(date, string, i) { return n ? (date.setSeconds(+n[0]), i += n[0].length) : -1; }
+function d3_time_parseMilliseconds(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.substring(i, i + 3)); + return n ? (date.setMilliseconds(+n[0]), i += n[0].length) : -1; +} + // Note: we don't look at the next directive. var d3_time_numberRe = /\s*\d+/;
@@ -294,18 +302,22 @@ function d3_time_year(d) { return new d3_time(d.getFullYear(), 0, 1); }
+function d3_time_daysElapsed(d0, d1) { + return ~~((d1 - d0) / 864e5 - (d1.getTimezoneOffset() - d0.getTimezoneOffset()) / 1440); +} + function d3_time_dayOfYear(d) { - return d3_time_zfill3(1 + ~~((d - d3_time_year(d)) / 864e5)); + return d3_time_zfill3(1 + d3_time_daysElapsed(d3_time_year(d), d)); }
function d3_time_weekNumberSunday(d) { var d0 = d3_time_year(d); - return d3_time_zfill2(~~(((d - d0) / 864e5 + d0.getDay()) / 7)); + return d3_time_zfill2(~~((d3_time_daysElapsed(d0, d) + d0.getDay()) / 7)); }
function d3_time_weekNumberMonday(d) { var d0 = d3_time_year(d); - return d3_time_zfill2(~~(((d - d0) / 864e5 + (d0.getDay() + 6) % 7) / 7)); + return d3_time_zfill2(~~((d3_time_daysElapsed(d0, d) + (d0.getDay() + 6) % 7) / 7)); }
// TODO table of time zone offset names? @@ -320,9 +332,14 @@ d3.time.format.utc = function(template) { var local = d3.time.format(template);
function format(date) { - var utc = new d3_time_format_utc(); - utc._ = date; - return local(utc); + try { + d3_time = d3_time_format_utc; + var utc = new d3_time(); + utc._ = date; + return local(utc); + } finally { + d3_time = Date; + } }
format.parse = function(string) { @@ -364,7 +381,19 @@ d3_time_format_utc.prototype = { setMonth: function(x) { this._.setUTCMonth(x); }, setSeconds: function(x) { this._.setUTCSeconds(x); } }; -d3.time.format.iso = d3.time.format.utc("%Y-%m-%dT%H:%M:%SZ"); +var d3_time_formatIso = d3.time.format.utc("%Y-%m-%dT%H:%M:%S.%LZ"); + +d3.time.format.iso = Date.prototype.toISOString ? d3_time_formatIsoNative : d3_time_formatIso; + +function d3_time_formatIsoNative(date) { + return date.toISOString(); +} + +d3_time_formatIsoNative.parse = function(string) { + return new Date(string); +}; + +d3_time_formatIsoNative.toString = d3_time_formatIso.toString; function d3_time_range(floor, step, number) { return function(t0, t1, dt) { var time = floor(t0), times = []; @@ -502,8 +531,7 @@ d3.time.years.utc = d3_time_range(d3.time.year.utc, function(date) { return date.getUTCFullYear(); }); // TODO nice -function d3_time_scale(methods, format) { - var linear = d3.scale.linear(); +function d3_time_scale(linear, methods, format) {
function scale(x) { return linear(x); @@ -537,13 +565,12 @@ function d3_time_scale(methods, format) { return format; };
- // TOOD expose d3_scale_linear_rebind? - scale.range = d3.rebind(scale, linear.range); - scale.rangeRound = d3.rebind(scale, linear.rangeRound); - scale.interpolate = d3.rebind(scale, linear.interpolate); - scale.clamp = d3.rebind(scale, linear.clamp); + scale.copy = function() { + return d3_time_scale(linear.copy(), methods, format); + };
- return scale; + // TOOD expose d3_scale_linear_rebind? + return d3.rebind(scale, linear, "range", "rangeRound", "interpolate", "clamp"); }
// TODO expose d3_scaleExtent? @@ -619,7 +646,7 @@ var d3_time_scaleLocalFormats = [ var d3_time_scaleLocalFormat = d3_time_scaleFormat(d3_time_scaleLocalFormats);
d3.time.scale = function() { - return d3_time_scale(d3_time_scaleLocalMethods, d3_time_scaleLocalFormat); + return d3_time_scale(d3.scale.linear(), d3_time_scaleLocalMethods, d3_time_scaleLocalFormat); }; var d3_time_scaleUTCMethods = [ [d3.time.seconds.utc, 1], @@ -655,6 +682,6 @@ var d3_time_scaleUTCFormats = [ var d3_time_scaleUTCFormat = d3_time_scaleFormat(d3_time_scaleUTCFormats);
d3.time.scale.utc = function() { - return d3_time_scale(d3_time_scaleUTCMethods, d3_time_scaleUTCFormat); + return d3_time_scale(d3.scale.linear(), d3_time_scaleUTCMethods, d3_time_scaleUTCFormat); }; })();
rhq-commits@lists.fedorahosted.org