This commit is contained in:
Mike Bostock 2013-06-13 15:47:51 -07:00
Родитель e15ac86caa f6108db93b
Коммит 84f4a62c01
75 изменённых файлов: 2538 добавлений и 1068 удалений

Просмотреть файл

@ -33,7 +33,7 @@ d3.min.js: d3.js
@rm -f $@
bin/uglify $< > $@
component.json: bin/component d3.js package.json
component.json: bin/component package.json
@rm -f $@
bin/component > $@
@chmod a-w $@

Просмотреть файл

@ -1,6 +1,6 @@
{
"name": "d3",
"version": "3.1.10",
"version": "3.2.0",
"main": "index-browserify.js",
"scripts": [
"index-browserify.js",

1425
d3.js поставляемый

Разница между файлами не показана из-за своего большого размера Загрузить разницу

10
d3.min.js поставляемый

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

23
lib/geographiclib/LICENSE Normal file
Просмотреть файл

@ -0,0 +1,23 @@
This license applies to GeographicLib, versions 1.12 and later.
Copyright (c) 2008-2012, Charles Karney
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use, copy,
modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

Просмотреть файл

@ -1,6 +1,6 @@
{
"name": "d3",
"version": "3.1.10",
"version": "3.2.0",
"description": "A small, free JavaScript library for manipulating documents based on data.",
"keywords": [
"dom",
@ -38,7 +38,7 @@
},
"devDependencies": {
"smash": "~0.0.8",
"uglify-js": "git://github.com/mishoo/UglifyJS2.git#3bd7ca9961125b39dcd54d2182cb72bd1ca6006e",
"uglify-js": "2.3.6",
"vows": "0.7.x"
},
"scripts": {

Просмотреть файл

@ -3,6 +3,7 @@ import "../core/rebind";
import "../event/event";
import "../event/mouse";
import "../event/touches";
import "../event/user-select";
import "behavior";
d3.behavior.drag = function() {
@ -21,7 +22,8 @@ d3.behavior.drag = function() {
touchId = d3.event.touches ? d3.event.changedTouches[0].identifier : null,
offset,
origin_ = point(),
moved = 0;
moved = 0,
selectEnable = d3_event_userSelectSuppress(touchId != null ? "drag-" + touchId : "drag");
var w = d3.select(d3_window)
.on(touchId != null ? "touchmove.drag-" + touchId : "mousemove.drag", dragmove)
@ -34,8 +36,6 @@ d3.behavior.drag = function() {
offset = [0, 0];
}
// Only cancel mousedown; touchstart is needed for draggable links.
if (touchId == null) d3_eventCancel();
event_({type: "dragstart"});
function point() {
@ -70,6 +70,7 @@ d3.behavior.drag = function() {
w .on(touchId != null ? "touchmove.drag-" + touchId : "mousemove.drag", null)
.on(touchId != null ? "touchend.drag-" + touchId : "mouseup.drag", null);
selectEnable();
}
}

Просмотреть файл

@ -3,6 +3,7 @@ import "../core/rebind";
import "../event/event";
import "../event/mouse";
import "../event/touches";
import "../event/user-select";
import "../selection/selection";
import "behavior";
@ -102,10 +103,8 @@ d3.behavior.zoom = function() {
eventTarget = d3.event.target,
moved = 0,
w = d3.select(d3_window).on("mousemove.zoom", mousemove).on("mouseup.zoom", mouseup),
l = location(d3.mouse(target));
d3_window.focus();
d3_eventCancel();
l = location(d3.mouse(target)),
selectEnable = d3_event_userSelectSuppress("zoom");
function mousemove() {
moved = 1;
@ -116,6 +115,7 @@ d3.behavior.zoom = function() {
function mouseup() {
if (moved) d3_eventCancel();
w.on("mousemove.zoom", null).on("mouseup.zoom", null);
selectEnable();
if (moved && d3.event.target === eventTarget) d3_eventSuppress(w, "click.zoom");
}
}
@ -145,7 +145,6 @@ d3.behavior.zoom = function() {
scale0 = scale;
translate0 = {};
touches.forEach(function(t) { translate0[t.identifier] = location(t); });
d3_eventCancel();
if (touches.length === 1) {
if (now - touchtime < 500) { // dbltap

Просмотреть файл

@ -13,7 +13,7 @@ function d3_arraySlice(pseudoarray) {
}
try {
d3_array(d3_document.documentElement.childNodes)[0].nodeType;
d3_array(d3_documentElement.childNodes)[0].nodeType;
} catch(e) {
d3_array = d3_arrayCopy;
}

Просмотреть файл

@ -1,2 +1,3 @@
var d3_document = document,
d3_documentElement = d3_document.documentElement,
d3_window = window;

12
src/core/vendor.js Normal file
Просмотреть файл

@ -0,0 +1,12 @@
import "document";
function d3_vendorSymbol(object, name) {
if (name in object) return name;
name = name.charAt(0).toUpperCase() + name.substring(1);
for (var i = 0, n = d3_vendorPrefixes.length; i < n; ++i) {
var prefixName = d3_vendorPrefixes[i] + name;
if (prefixName in object) return prefixName;
}
}
var d3_vendorPrefixes = ["webkit", "ms", "moz", "Moz", "o", "O"];

Просмотреть файл

@ -1,3 +1,3 @@
import "dsv";
d3.csv = d3_dsv(",", "text/csv");
d3.csv = d3.dsv(",", "text/csv");

Просмотреть файл

@ -1,7 +1,7 @@
import "../arrays/set";
import "../xhr/xhr";
function d3_dsv(delimiter, mimeType) {
d3.dsv = function(delimiter, mimeType) {
var reFormat = new RegExp("[\"" + delimiter + "\n]"),
delimiterCode = delimiter.charCodeAt(0);
@ -133,4 +133,4 @@ function d3_dsv(delimiter, mimeType) {
}
return dsv;
}
};

Просмотреть файл

@ -1,3 +1,3 @@
import "dsv";
d3.tsv = d3_dsv("\t", "text/tab-separated-values");
d3.tsv = d3.dsv("\t", "text/tab-separated-values");

Просмотреть файл

@ -12,10 +12,14 @@ function d3_mousePoint(container, e) {
if (svg.createSVGPoint) {
var point = svg.createSVGPoint();
if (d3_mouse_bug44083 < 0 && (d3_window.scrollX || d3_window.scrollY)) {
svg = d3.select(d3_document.body).append("svg")
.style("position", "absolute")
.style("top", 0)
.style("left", 0);
svg = d3.select("body").append("svg").style({
position: "absolute",
top: 0,
left: 0,
margin: 0,
padding: 0,
border: "none"
}, "important");
var ctm = svg[0][0].getScreenCTM();
d3_mouse_bug44083 = !(ctm.f || ctm.e);
svg.remove();

Просмотреть файл

@ -1,4 +1,5 @@
import "../core/document";
import "../core/vendor";
var d3_timer_queueHead,
d3_timer_queueTail,
@ -77,9 +78,5 @@ function d3_timer_sweep() {
return time;
}
var d3_timer_frame = d3_window.requestAnimationFrame
|| d3_window.webkitRequestAnimationFrame
|| d3_window.mozRequestAnimationFrame
|| d3_window.oRequestAnimationFrame
|| d3_window.msRequestAnimationFrame
var d3_timer_frame = d3_window[d3_vendorSymbol(d3_window, "requestAnimationFrame")]
|| function(callback) { setTimeout(callback, 17); };

16
src/event/user-select.js Normal file
Просмотреть файл

@ -0,0 +1,16 @@
import "../core/document";
import "../core/vendor";
import "event";
var d3_event_userSelectProperty = d3_vendorSymbol(d3_documentElement.style, "userSelect"),
d3_event_userSelectSuppress = d3_event_userSelectProperty
? function() {
var style = d3_documentElement.style,
select = style[d3_event_userSelectProperty];
style[d3_event_userSelectProperty] = "none";
return function() { style[d3_event_userSelectProperty] = select; };
}
: function(type) {
var w = d3.select(d3_window).on("selectstart." + type, d3_eventCancel);
return function() { w.on("selectstart." + type, null); };
};

Просмотреть файл

@ -1,4 +1,5 @@
import "../core/noop";
import "../math/adder";
import "../math/trigonometry";
import "geo";
import "stream";
@ -10,7 +11,7 @@ d3.geo.area = function(object) {
};
var d3_geo_areaSum,
d3_geo_areaRingSum;
d3_geo_areaRingSum = new d3_adder;
var d3_geo_area = {
sphere: function() { d3_geo_areaSum += 4 * π; },
@ -20,7 +21,7 @@ var d3_geo_area = {
// Only count area for polygon rings.
polygonStart: function() {
d3_geo_areaRingSum = 0;
d3_geo_areaRingSum.reset();
d3_geo_area.lineStart = d3_geo_areaRingStart;
},
polygonEnd: function() {
@ -31,7 +32,7 @@ var d3_geo_area = {
};
function d3_geo_areaRingStart() {
var λ00, φ00, λ0, cosφ0, sinφ0; // start point and two previous points
var λ00, φ00, λ0, cosφ0, sinφ0; // start point and previous point
// For the first point, …
d3_geo_area.point = function(λ, φ) {
@ -53,7 +54,7 @@ function d3_geo_areaRingStart() {
k = sinφ0 * sinφ,
u = cosφ0 * cosφ + k * Math.cos(),
v = k * Math.sin();
d3_geo_areaRingSum += Math.atan2(v, u);
d3_geo_areaRingSum.add(Math.atan2(v, u));
// Advance the previous points.
λ0 = λ, cosφ0 = cosφ, sinφ0 = sinφ;

Просмотреть файл

@ -136,29 +136,34 @@ d3.geo.bounds = (function() {
d3.geo.stream(feature, bound);
// First, sort ranges by their minimum longitudes.
ranges.sort(compareRanges);
var n = ranges.length;
if (n) {
// First, sort ranges by their minimum longitudes.
ranges.sort(compareRanges);
// Then, merge any ranges that overlap.
for (var i = 1, n = ranges.length, a = ranges[0], b, merged = [a]; i < n; ++i) {
b = ranges[i];
if (withinRange(b[0], a) || withinRange(b[1], a)) {
if (angle(a[0], b[1]) > angle(a[0], a[1])) a[1] = b[1];
if (angle(b[0], a[1]) > angle(a[0], a[1])) a[0] = b[0];
} else {
merged.push(a = b);
// Then, merge any ranges that overlap.
for (var i = 1, a = ranges[0], b, merged = [a]; i < n; ++i) {
b = ranges[i];
if (withinRange(b[0], a) || withinRange(b[1], a)) {
if (angle(a[0], b[1]) > angle(a[0], a[1])) a[1] = b[1];
if (angle(b[0], a[1]) > angle(a[0], a[1])) a[0] = b[0];
} else {
merged.push(a = b);
}
}
}
// Finally, find the largest gap between the merged ranges.
// The final bounding box will be the inverse of this gap.
var best = -Infinity, ;
for (var n = merged.length - 1, i = 0, a = merged[n], b; i <= n; a = b, ++i) {
b = merged[i];
if (( = angle(a[1], b[0])) > best) best = , λ0 = b[0], λ1 = a[1];
// Finally, find the largest gap between the merged ranges.
// The final bounding box will be the inverse of this gap.
var best = -Infinity, ;
for (var n = merged.length - 1, i = 0, a = merged[n], b; i <= n; a = b, ++i) {
b = merged[i];
if (( = angle(a[1], b[0])) > best) best = , λ0 = b[0], λ1 = a[1];
}
}
ranges = range = null;
return [[λ0, φ0], [λ1, φ1]];
return λ0 === Infinity || φ0 === Infinity
? [[NaN, NaN], [NaN, NaN]]
: [[λ0, φ0], [λ1, φ1]];
};
})();

Просмотреть файл

@ -1,41 +1,51 @@
import "../core/noop";
import "../math/trigonometry";
import "geo";
import "stream";
d3.geo.centroid = function(object) {
d3_geo_centroidDimension = d3_geo_centroidW = d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0;
d3_geo_centroidW0 = d3_geo_centroidW1 =
d3_geo_centroidX0 = d3_geo_centroidY0 = d3_geo_centroidZ0 =
d3_geo_centroidX1 = d3_geo_centroidY1 = d3_geo_centroidZ1 =
d3_geo_centroidX2 = d3_geo_centroidY2 = d3_geo_centroidZ2 = 0;
d3.geo.stream(object, d3_geo_centroid);
var m;
if (d3_geo_centroidW &&
Math.abs(m = Math.sqrt(d3_geo_centroidX * d3_geo_centroidX + d3_geo_centroidY * d3_geo_centroidY + d3_geo_centroidZ * d3_geo_centroidZ)) > ε) {
return [
Math.atan2(d3_geo_centroidY, d3_geo_centroidX) * d3_degrees,
Math.asin(Math.max(-1, Math.min(1, d3_geo_centroidZ / m))) * d3_degrees
];
var x = d3_geo_centroidX2,
y = d3_geo_centroidY2,
z = d3_geo_centroidZ2,
m = x * x + y * y + z * z;
// If the area-weighted centroid is undefined, fall back to length-weighted centroid.
if (m < ε2) {
x = d3_geo_centroidX1, y = d3_geo_centroidY1, z = d3_geo_centroidZ1;
// If the feature has zero length, fall back to arithmetic mean of point vectors.
if (d3_geo_centroidW1 < ε) x = d3_geo_centroidX0, y = d3_geo_centroidY0, z = d3_geo_centroidZ0;
m = x * x + y * y + z * z;
// If the feature still has an undefined centroid, then return.
if (m < ε2) return [NaN, NaN];
}
return [Math.atan2(y, x) * d3_degrees, d3_asin(z / Math.sqrt(m)) * d3_degrees];
};
var d3_geo_centroidDimension,
d3_geo_centroidW,
d3_geo_centroidX,
d3_geo_centroidY,
d3_geo_centroidZ;
var d3_geo_centroidW0,
d3_geo_centroidW1,
d3_geo_centroidX0,
d3_geo_centroidY0,
d3_geo_centroidZ0,
d3_geo_centroidX1,
d3_geo_centroidY1,
d3_geo_centroidZ1,
d3_geo_centroidX2,
d3_geo_centroidY2,
d3_geo_centroidZ2;
var d3_geo_centroid = {
sphere: function() {
if (d3_geo_centroidDimension < 2) {
d3_geo_centroidDimension = 2;
d3_geo_centroidW = d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0;
}
},
sphere: d3_noop,
point: d3_geo_centroidPoint,
lineStart: d3_geo_centroidLineStart,
lineEnd: d3_geo_centroidLineEnd,
polygonStart: function() {
if (d3_geo_centroidDimension < 2) {
d3_geo_centroidDimension = 2;
d3_geo_centroidW = d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0;
}
d3_geo_centroid.lineStart = d3_geo_centroidRingStart;
},
polygonEnd: function() {
@ -45,42 +55,21 @@ var d3_geo_centroid = {
// Arithmetic mean of Cartesian vectors.
function d3_geo_centroidPoint(λ, φ) {
if (d3_geo_centroidDimension) return;
++d3_geo_centroidW;
λ *= d3_radians;
var cosφ = Math.cos(φ *= d3_radians);
d3_geo_centroidX += (cosφ * Math.cos(λ) - d3_geo_centroidX) / d3_geo_centroidW;
d3_geo_centroidY += (cosφ * Math.sin(λ) - d3_geo_centroidY) / d3_geo_centroidW;
d3_geo_centroidZ += (Math.sin(φ) - d3_geo_centroidZ) / d3_geo_centroidW;
d3_geo_centroidPointXYZ(cosφ * Math.cos(λ), cosφ * Math.sin(λ), Math.sin(φ));
}
function d3_geo_centroidRingStart() {
var λ00, φ00; // first point
d3_geo_centroidDimension = 1;
d3_geo_centroidLineStart();
d3_geo_centroidDimension = 2;
var linePoint = d3_geo_centroid.point;
d3_geo_centroid.point = function(λ, φ) {
linePoint(λ00 = λ, φ00 = φ);
};
d3_geo_centroid.lineEnd = function() {
d3_geo_centroid.point(λ00, φ00);
d3_geo_centroidLineEnd();
d3_geo_centroid.lineEnd = d3_geo_centroidLineEnd;
};
function d3_geo_centroidPointXYZ(x, y, z) {
++d3_geo_centroidW0;
d3_geo_centroidX0 += (x - d3_geo_centroidX0) / d3_geo_centroidW0;
d3_geo_centroidY0 += (y - d3_geo_centroidY0) / d3_geo_centroidW0;
d3_geo_centroidZ0 += (z - d3_geo_centroidZ0) / d3_geo_centroidW0;
}
function d3_geo_centroidLineStart() {
var x0, y0, z0; // previous point
if (d3_geo_centroidDimension > 1) return;
if (d3_geo_centroidDimension < 1) {
d3_geo_centroidDimension = 1;
d3_geo_centroidW = d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0;
}
d3_geo_centroid.point = function(λ, φ) {
λ *= d3_radians;
var cosφ = Math.cos(φ *= d3_radians);
@ -88,6 +77,7 @@ function d3_geo_centroidLineStart() {
y0 = cosφ * Math.sin(λ);
z0 = Math.sin(φ);
d3_geo_centroid.point = nextPoint;
d3_geo_centroidPointXYZ(x0, y0, z0);
};
function nextPoint(λ, φ) {
@ -99,13 +89,61 @@ function d3_geo_centroidLineStart() {
w = Math.atan2(
Math.sqrt((w = y0 * z - z0 * y) * w + (w = z0 * x - x0 * z) * w + (w = x0 * y - y0 * x) * w),
x0 * x + y0 * y + z0 * z);
d3_geo_centroidW += w;
d3_geo_centroidX += w * (x0 + (x0 = x));
d3_geo_centroidY += w * (y0 + (y0 = y));
d3_geo_centroidZ += w * (z0 + (z0 = z));
d3_geo_centroidW1 += w;
d3_geo_centroidX1 += w * (x0 + (x0 = x));
d3_geo_centroidY1 += w * (y0 + (y0 = y));
d3_geo_centroidZ1 += w * (z0 + (z0 = z));
d3_geo_centroidPointXYZ(x0, y0, z0);
}
}
function d3_geo_centroidLineEnd() {
d3_geo_centroid.point = d3_geo_centroidPoint;
}
// See J. E. Brock, The Inertia Tensor for a Spherical Triangle,
// J. Applied Mechanics 42, 239 (1975).
function d3_geo_centroidRingStart() {
var λ00, φ00, // first point
x0, y0, z0; // previous point
d3_geo_centroid.point = function(λ, φ) {
λ00 = λ, φ00 = φ;
d3_geo_centroid.point = nextPoint;
λ *= d3_radians;
var cosφ = Math.cos(φ *= d3_radians);
x0 = cosφ * Math.cos(λ);
y0 = cosφ * Math.sin(λ);
z0 = Math.sin(φ);
d3_geo_centroidPointXYZ(x0, y0, z0);
};
d3_geo_centroid.lineEnd = function() {
nextPoint(λ00, φ00);
d3_geo_centroid.lineEnd = d3_geo_centroidLineEnd;
d3_geo_centroid.point = d3_geo_centroidPoint;
};
function nextPoint(λ, φ) {
λ *= d3_radians;
var cosφ = Math.cos(φ *= d3_radians),
x = cosφ * Math.cos(λ),
y = cosφ * Math.sin(λ),
z = Math.sin(φ),
cx = y0 * z - z0 * y,
cy = z0 * x - x0 * z,
cz = x0 * y - y0 * x,
m = Math.sqrt(cx * cx + cy * cy + cz * cz),
u = x0 * x + y0 * y + z0 * z,
v = m && -d3_acos(u) / m, // area weight
w = Math.atan2(m, u); // line weight
d3_geo_centroidX2 += v * cx;
d3_geo_centroidY2 += v * cy;
d3_geo_centroidZ2 += v * cz;
d3_geo_centroidW1 += w;
d3_geo_centroidX1 += w * (x0 + (x0 = x));
d3_geo_centroidY1 += w * (y0 + (y0 = y));
d3_geo_centroidZ1 += w * (z0 + (z0 = z));
d3_geo_centroidPointXYZ(x0, y0, z0);
}
}

Просмотреть файл

@ -1,8 +1,13 @@
import "../core/true";
import "../math/trigonometry";
import "clip";
import "point-in-polygon";
var d3_geo_clipAntimeridian = d3_geo_clip(d3_true, d3_geo_clipAntimeridianLine, d3_geo_clipAntimeridianInterpolate);
var d3_geo_clipAntimeridian = d3_geo_clip(
d3_true,
d3_geo_clipAntimeridianLine,
d3_geo_clipAntimeridianInterpolate,
d3_geo_clipAntimeridianPolygonContains);
// Takes a line and cuts into visible segments. Return values:
// 0: there were intersections or the line was empty.
@ -88,3 +93,9 @@ function d3_geo_clipAntimeridianInterpolate(from, to, direction, listener) {
listener.point(to[0], to[1]);
}
}
var d3_geo_clipAntimeridianPoint = [-π, 0];
function d3_geo_clipAntimeridianPolygonContains(polygon) {
return d3_geo_pointInPolygon(d3_geo_clipAntimeridianPoint, polygon);
}

Просмотреть файл

@ -3,15 +3,17 @@ import "cartesian";
import "clip";
import "circle";
import "spherical";
import "point-in-polygon";
// Clip features against a small circle centered at [0°, 0°].
function d3_geo_clipCircle(radius) {
var cr = Math.cos(radius),
smallRadius = cr > 0,
point = [radius, 0],
notHemisphere = Math.abs(cr) > ε, // TODO optimise for this common case
interpolate = d3_geo_circleInterpolate(radius, 6 * d3_radians);
return d3_geo_clip(visible, clipLine, interpolate);
return d3_geo_clip(visible, clipLine, interpolate, polygonContains);
function visible(λ, φ) {
return Math.cos(λ) * Math.cos(φ) > cr;
@ -173,4 +175,8 @@ function d3_geo_clipCircle(radius) {
else if (φ > r) code |= 8; // above
return code;
}
function polygonContains(polygon) {
return d3_geo_pointInPolygon(point, polygon);
}
}

Просмотреть файл

@ -3,7 +3,7 @@ import "../core/noop";
import "../math/trigonometry";
import "clip-polygon";
function d3_geo_clip(pointVisible, clipLine, interpolate) {
function d3_geo_clip(pointVisible, clipLine, interpolate, polygonContains) {
return function(listener) {
var line = clipLine(listener);
@ -15,9 +15,8 @@ function d3_geo_clip(pointVisible, clipLine, interpolate) {
clip.point = pointRing;
clip.lineStart = ringStart;
clip.lineEnd = ringEnd;
invisible = false;
invisibleArea = visibleArea = 0;
segments = [];
polygon = [];
listener.polygonStart();
},
polygonEnd: function() {
@ -28,13 +27,13 @@ function d3_geo_clip(pointVisible, clipLine, interpolate) {
segments = d3.merge(segments);
if (segments.length) {
d3_geo_clipPolygon(segments, d3_geo_clipSort, null, interpolate, listener);
} else if (visibleArea < -ε || invisible && invisibleArea < -ε) {
} else if (polygonContains(polygon)) {
listener.lineStart();
interpolate(null, null, 1, listener);
listener.lineEnd();
}
listener.polygonEnd();
segments = null;
segments = polygon = null;
},
sphere: function() {
listener.polygonStart();
@ -50,13 +49,11 @@ function d3_geo_clip(pointVisible, clipLine, interpolate) {
function lineStart() { clip.point = pointLine; line.lineStart(); }
function lineEnd() { clip.point = point; line.lineEnd(); }
var segments,
visibleArea,
invisibleArea,
invisible;
var segments;
var buffer = d3_geo_clipBufferListener(),
ringListener = clipLine(buffer),
polygon,
ring;
function pointRing(λ, φ) {
@ -78,20 +75,15 @@ function d3_geo_clip(pointVisible, clipLine, interpolate) {
segment,
n = ringSegments.length;
// TODO compute on-the-fly?
if (!n) {
invisible = true;
invisibleArea += d3_geo_clipAreaRing(ring, -1);
ring = null;
return;
}
ring.pop();
polygon.push(ring);
ring = null;
if (!n) return;
// No intersections.
// TODO compute on-the-fly?
if (clean & 1) {
segment = ringSegments[0];
visibleArea += d3_geo_clipAreaRing(segment, 1);
var n = segment.length - 1,
i = -1,
point;
@ -135,59 +127,6 @@ function d3_geo_clipBufferListener() {
};
}
// Approximate polygon ring area (×2, since we only need the sign).
// For an invisible polygon ring, we rotate longitudinally by 180°.
// The invisible parameter should be 1, or -1 to rotate longitudinally.
// Based on Robert. G. Chamberlain and William H. Duquette,
// “Some Algorithms for Polygons on a Sphere”,
// http://trs-new.jpl.nasa.gov/dspace/handle/2014/40409
function d3_geo_clipAreaRing(ring, invisible) {
if (!(n = ring.length)) return 0;
var n,
i = 0,
area = 0,
p = ring[0],
λ = p[0],
φ = p[1],
cosφ = Math.cos(φ),
x0 = Math.atan2(invisible * Math.sin(λ) * cosφ, Math.sin(φ)),
y0 = 1 - invisible * Math.cos(λ) * cosφ,
x1 = x0,
x, // λ'; λ rotated to south pole.
y; // φ' = 1 + sin(φ); φ rotated to south pole.
while (++i < n) {
p = ring[i];
cosφ = Math.cos(φ = p[1]);
x = Math.atan2(invisible * Math.sin(λ = p[0]) * cosφ, Math.sin(φ));
y = 1 - invisible * Math.cos(λ) * cosφ;
// If both the current point and the previous point are at the north pole,
// skip this point.
if (Math.abs(y0 - 2) < ε && Math.abs(y - 2) < ε) continue;
// If this or the previous point is at the south pole, or if this segment
// goes through the south pole, the area is 0.
if (Math.abs(y) < ε || Math.abs(y0) < ε) {}
// If this segment goes through either pole…
else if (Math.abs(Math.abs(x - x0) - π) < ε) {
// For the north pole, compute lune area.
if (y + y0 > 2) area += 4 * (x - x0);
// For the south pole, the area is zero.
}
// If the previous point is at the north pole, then compute lune area.
else if (Math.abs(y0 - 2) < ε) area += 4 * (x - x1);
// Otherwise, the spherical triangle area is approximately
// δλ * (1 + sinφ0 + 1 + sinφ) / 2.
else area += ((3 * π + x - x0) % (2 * π) - π) * (y0 + y);
x1 = x0, x0 = x, y0 = y;
}
return area;
}
// Intersection points are sorted along the clip edge. For both antimeridian
// cutting and circle clipping, the same comparison is used.
function d3_geo_clipSort(a, b) {

Просмотреть файл

@ -22,33 +22,25 @@ var d3_geo_pathCentroid = {
};
function d3_geo_pathCentroidPoint(x, y) {
if (d3_geo_centroidDimension) return;
d3_geo_centroidX += x;
d3_geo_centroidY += y;
++d3_geo_centroidZ;
d3_geo_centroidX0 += x;
d3_geo_centroidY0 += y;
++d3_geo_centroidZ0;
}
function d3_geo_pathCentroidLineStart() {
var x0, y0;
if (d3_geo_centroidDimension !== 1) {
if (d3_geo_centroidDimension < 1) {
d3_geo_centroidDimension = 1;
d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0;
} else return;
}
d3_geo_pathCentroid.point = function(x, y) {
d3_geo_pathCentroid.point = nextPoint;
x0 = x, y0 = y;
d3_geo_pathCentroidPoint(x0 = x, y0 = y);
};
function nextPoint(x, y) {
var dx = x - x0, dy = y - y0, z = Math.sqrt(dx * dx + dy * dy);
d3_geo_centroidX += z * (x0 + x) / 2;
d3_geo_centroidY += z * (y0 + y) / 2;
d3_geo_centroidZ += z;
x0 = x, y0 = y;
d3_geo_centroidX1 += z * (x0 + x) / 2;
d3_geo_centroidY1 += z * (y0 + y) / 2;
d3_geo_centroidZ1 += z;
d3_geo_pathCentroidPoint(x0 = x, y0 = y);
}
}
@ -59,24 +51,24 @@ function d3_geo_pathCentroidLineEnd() {
function d3_geo_pathCentroidRingStart() {
var x00, y00, x0, y0;
if (d3_geo_centroidDimension < 2) {
d3_geo_centroidDimension = 2;
d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0;
}
// For the first point, …
d3_geo_pathCentroid.point = function(x, y) {
d3_geo_pathCentroid.point = nextPoint;
x00 = x0 = x, y00 = y0 = y;
d3_geo_pathCentroidPoint(x00 = x0 = x, y00 = y0 = y);
};
// For subsequent points, …
function nextPoint(x, y) {
var z = y0 * x - x0 * y;
d3_geo_centroidX += z * (x0 + x);
d3_geo_centroidY += z * (y0 + y);
d3_geo_centroidZ += z * 3;
x0 = x, y0 = y;
var dx = x - x0, dy = y - y0, z = Math.sqrt(dx * dx + dy * dy);
d3_geo_centroidX1 += z * (x0 + x) / 2;
d3_geo_centroidY1 += z * (y0 + y) / 2;
d3_geo_centroidZ1 += z;
z = y0 * x - x0 * y;
d3_geo_centroidX2 += z * (x0 + x);
d3_geo_centroidY2 += z * (y0 + y);
d3_geo_centroidZ2 += z * 3;
d3_geo_pathCentroidPoint(x0 = x, y0 = y);
}
// For the last point, return to the start.

Просмотреть файл

@ -19,13 +19,15 @@ d3.geo.path = function() {
projection,
context,
projectStream,
contextStream;
contextStream,
cacheStream;
function path(object) {
if (object) d3.geo.stream(object, projectStream(
contextStream.pointRadius(typeof pointRadius === "function"
? +pointRadius.apply(this, arguments)
: pointRadius)));
if (object) {
if (typeof pointRadius === "function") contextStream.pointRadius(+pointRadius.apply(this, arguments));
if (!cacheStream || !cacheStream.valid) cacheStream = projectStream(contextStream);
d3.geo.stream(object, cacheStream);
}
return contextStream.result();
}
@ -36,9 +38,14 @@ d3.geo.path = function() {
};
path.centroid = function(object) {
d3_geo_centroidDimension = d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0;
d3_geo_centroidX0 = d3_geo_centroidY0 = d3_geo_centroidZ0 =
d3_geo_centroidX1 = d3_geo_centroidY1 = d3_geo_centroidZ1 =
d3_geo_centroidX2 = d3_geo_centroidY2 = d3_geo_centroidZ2 = 0;
d3.geo.stream(object, projectStream(d3_geo_pathCentroid));
return d3_geo_centroidZ ? [d3_geo_centroidX / d3_geo_centroidZ, d3_geo_centroidY / d3_geo_centroidZ] : undefined;
return d3_geo_centroidZ2 ? [d3_geo_centroidX2 / d3_geo_centroidZ2, d3_geo_centroidY2 / d3_geo_centroidZ2]
: d3_geo_centroidZ1 ? [d3_geo_centroidX1 / d3_geo_centroidZ1, d3_geo_centroidY1 / d3_geo_centroidZ1]
: d3_geo_centroidZ0 ? [d3_geo_centroidX0 / d3_geo_centroidZ0, d3_geo_centroidY0 / d3_geo_centroidZ0]
: [NaN, NaN];
};
path.bounds = function(object) {
@ -50,21 +57,27 @@ d3.geo.path = function() {
path.projection = function(_) {
if (!arguments.length) return projection;
projectStream = (projection = _) ? _.stream || d3_geo_pathProjectStream(_) : d3_identity;
return path;
return reset();
};
path.context = function(_) {
if (!arguments.length) return context;
contextStream = (context = _) == null ? new d3_geo_pathBuffer : new d3_geo_pathContext(_);
return path;
if (typeof pointRadius !== "function") contextStream.pointRadius(pointRadius);
return reset();
};
path.pointRadius = function(_) {
if (!arguments.length) return pointRadius;
pointRadius = typeof _ === "function" ? _ : +_;
pointRadius = typeof _ === "function" ? _ : (contextStream.pointRadius(+_), +_);
return path;
};
function reset() {
cacheStream = null;
return path;
}
return path.projection(d3.geo.albersUsa()).context(null);
};

Просмотреть файл

@ -0,0 +1,71 @@
import "geo";
import "area";
import "cartesian";
import "../math/trigonometry";
function d3_geo_pointInPolygon(point, polygon) {
var meridian = point[0],
parallel = point[1],
meridianNormal = [Math.sin(meridian), -Math.cos(meridian), 0],
polarAngle = 0,
polar = false,
southPole = false,
winding = 0;
d3_geo_areaRingSum.reset();
for (var i = 0, n = polygon.length; i < n; ++i) {
var ring = polygon[i],
m = ring.length;
if (!m) continue;
var point0 = ring[0],
λ0 = point0[0],
φ0 = point0[1] / 2 + π / 4,
sinφ0 = Math.sin(φ0),
cosφ0 = Math.cos(φ0),
j = 1;
while (true) {
if (j === m) j = 0;
point = ring[j];
var λ = point[0],
φ = point[1] / 2 + π / 4,
sinφ = Math.sin(φ),
cosφ = Math.cos(φ),
= λ - λ0,
antimeridian = Math.abs() > π,
k = sinφ0 * sinφ;
d3_geo_areaRingSum.add(Math.atan2(k * Math.sin(), cosφ0 * cosφ + k * Math.cos()));
if (Math.abs(φ) < ε) southPole = true;
polarAngle += antimeridian ? + ( >= 0 ? 2 : -2) * π : ;
// Are the longitudes either side of the point's meridian, and are the
// latitudes smaller than the parallel?
if (antimeridian ^ λ0 >= meridian ^ λ >= meridian) {
var arc = d3_geo_cartesianCross(d3_geo_cartesian(point0), d3_geo_cartesian(point));
d3_geo_cartesianNormalize(arc);
var intersection = d3_geo_cartesianCross(meridianNormal, arc);
d3_geo_cartesianNormalize(intersection);
var φarc = (antimeridian ^ >= 0 ? -1 : 1) * d3_asin(intersection[2]);
if (parallel > φarc) {
winding += antimeridian ^ >= 0 ? 1 : -1;
}
}
if (!j++) break;
λ0 = λ, sinφ0 = sinφ, cosφ0 = cosφ, point0 = point;
}
if (Math.abs(polarAngle) > ε) polar = true;
}
// First, determine whether the South pole is inside or outside:
//
// It is inside if:
// * the polygon doesn't wind around it, and its area is negative (counter-clockwise).
// * otherwise, if the polygon winds around it in a clockwise direction.
//
// Second, count the (signed) number of times a segment crosses a meridian
// from the point to the South pole. If it is zero, then the point is the
// same side as the South pole.
return (!southPole && !polar && d3_geo_areaRingSum < 0 || polarAngle < -ε) ^ (winding & 1);
}

Просмотреть файл

@ -6,6 +6,7 @@ import "clip-circle";
import "clip-view";
import "compose";
import "geo";
import "path";
import "resample";
import "rotation";
import "stream";
@ -30,7 +31,8 @@ function d3_geo_projectionMutator(projectAt) {
preclip = d3_geo_clipAntimeridian,
postclip = d3_identity,
clipAngle = null,
clipExtent = null;
clipExtent = null,
stream;
function projection(point) {
point = projectRotate(point[0] * d3_radians, point[1] * d3_radians);
@ -42,21 +44,24 @@ function d3_geo_projectionMutator(projectAt) {
return point && [point[0] * d3_degrees, point[1] * d3_degrees];
}
projection.stream = function(stream) {
return d3_geo_projectionRadiansRotate(rotate, preclip(projectResample(postclip(stream))));
projection.stream = function(output) {
if (stream) stream.valid = false;
stream = d3_geo_projectionRadiansRotate(rotate, preclip(projectResample(postclip(output))));
stream.valid = true; // allow caching by d3.geo.path
return stream;
};
projection.clipAngle = function(_) {
if (!arguments.length) return clipAngle;
preclip = _ == null ? (clipAngle = _, d3_geo_clipAntimeridian) : d3_geo_clipCircle((clipAngle = +_) * d3_radians);
return projection;
return invalidate();
};
projection.clipExtent = function(_) {
if (!arguments.length) return clipExtent;
clipExtent = _;
postclip = _ == null ? d3_identity : d3_geo_clipView(_[0][0], _[0][1], _[1][0], _[1][1]);
return projection;
return invalidate();
};
projection.scale = function(_) {
@ -94,6 +99,14 @@ function d3_geo_projectionMutator(projectAt) {
var center = project(λ, φ);
δx = x - center[0] * k;
δy = y + center[1] * k;
return invalidate();
}
function invalidate() {
if (stream) {
stream.valid = false;
stream = null;
}
return projection;
}

Просмотреть файл

@ -6,13 +6,14 @@ function d3_geo_resample(project) {
maxDepth = 16;
function resample(stream) {
var λ0, x0, y0, a0, b0, c0; // previous point
var λ00, φ00, x00, y00, a00, b00, c00, // first point
λ0, x0, y0, a0, b0, c0; // previous point
var resample = {
point: point,
lineStart: lineStart,
lineEnd: lineEnd,
polygonStart: function() { stream.polygonStart(); resample.lineStart = polygonLineStart; },
polygonStart: function() { stream.polygonStart(); resample.lineStart = ringStart; },
polygonEnd: function() { stream.polygonEnd(); resample.lineStart = lineStart; }
};
@ -28,8 +29,9 @@ function d3_geo_resample(project) {
}
function linePoint(λ, φ) {
var c = d3_geo_cartesian([λ, φ]), p = project(λ, φ);
resampleLineTo(x0, y0, λ0, a0, b0, c0, x0 = p[0], y0 = p[1], λ0 = λ, a0 = c[0], b0 = c[1], c0 = c[2], maxDepth, stream);
var c = d3_geo_cartesian([λ, φ]), p = project(λ, φ), buffer = [];
resampleLineTo(x0, y0, λ0, a0, b0, c0, x0 = p[0], y0 = p[1], λ0 = λ, a0 = c[0], b0 = c[1], c0 = c[2], maxDepth, buffer);
streamLine(buffer, stream);
stream.point(x0, y0);
}
@ -38,27 +40,35 @@ function d3_geo_resample(project) {
stream.lineEnd();
}
function polygonLineStart() {
var λ00, φ00, x00, y00, a00, b00, c00; // first point
function ringStart() {
lineStart();
resample.point = ringPoint;
resample.lineEnd = ringEnd;
}
resample.point = function(λ, φ) {
linePoint(λ00 = λ, φ00 = φ), x00 = x0, y00 = y0, a00 = a0, b00 = b0, c00 = c0;
resample.point = linePoint;
};
function ringPoint(λ, φ) {
linePoint(λ00 = λ, φ00 = φ), x00 = x0, y00 = y0, a00 = a0, b00 = b0, c00 = c0;
resample.point = linePoint;
}
resample.lineEnd = function() {
resampleLineTo(x0, y0, λ0, a0, b0, c0, x00, y00, λ00, a00, b00, c00, maxDepth, stream);
resample.lineEnd = lineEnd;
lineEnd();
};
function ringEnd() {
var buffer = [];
resampleLineTo(x0, y0, λ0, a0, b0, c0, x00, y00, λ00, a00, b00, c00, maxDepth, buffer);
streamLine(buffer, stream);
resample.lineEnd = lineEnd;
lineEnd();
}
function streamLine(line, stream) {
for (var i = 0, n = line.length, point; i < n; ++i) {
stream.point((point = line[i])[0], point[1]);
}
}
return resample;
}
function resampleLineTo(x0, y0, λ0, a0, b0, c0, x1, y1, λ1, a1, b1, c1, depth, stream) {
function resampleLineTo(x0, y0, λ0, a0, b0, c0, x1, y1, λ1, a1, b1, c1, depth, buffer) {
var dx = x1 - x0,
dy = y1 - y0,
d2 = dx * dx + dy * dy;
@ -74,11 +84,13 @@ function d3_geo_resample(project) {
y2 = p[1],
dx2 = x2 - x0,
dy2 = y2 - y0,
dz = dy * dx2 - dx * dy2;
if (dz * dz / d2 > δ2 || Math.abs((dx * dx2 + dy * dy2) / d2 - .5) > .3) {
resampleLineTo(x0, y0, λ0, a0, b0, c0, x2, y2, λ2, a /= m, b /= m, c, depth, stream);
stream.point(x2, y2);
resampleLineTo(x2, y2, λ2, a, b, c, x1, y1, λ1, a1, b1, c1, depth, stream);
dz = dy * dx2 - dx * dy2,
tooFar = false;
if (dz * dz / d2 > δ2 || Math.abs((dx * dx2 + dy * dy2) / d2 - .5) > .3 || (tooFar = dx2 * dx2 + dy2 * dy2 > 256 * δ2)) {
var s0 = resampleLineTo(x0, y0, λ0, a0, b0, c0, x2, y2, λ2, a /= m, b /= m, c, depth, buffer);
buffer.push(p);
var s1 = resampleLineTo(x2, y2, λ2, a, b, c, x1, y1, λ1, a1, b1, c1, depth, buffer);
return !tooFar || s0 || s1 || (buffer.pop(), false);
}
}
}

Просмотреть файл

@ -52,7 +52,7 @@ function d3_geo_rotationφγ(δφ, δγ) {
k = z * cosδφ + x * sinδφ;
return [
Math.atan2(y * cosδγ - k * sinδγ, x * cosδφ - z * sinδφ),
Math.asin(Math.max(-1, Math.min(1, k * cosδγ + y * sinδγ)))
d3_asin(k * cosδγ + y * sinδγ)
];
}
@ -64,7 +64,7 @@ function d3_geo_rotationφγ(δφ, δγ) {
k = z * cosδγ - y * sinδγ;
return [
Math.atan2(y * cosδγ + z * sinδγ, x * cosδφ + k * sinδφ),
Math.asin(Math.max(-1, Math.min(1, k * cosδφ - x * sinδφ)))
d3_asin(k * cosδφ - x * sinδφ)
];
};

Просмотреть файл

@ -3,7 +3,7 @@ import "../math/trigonometry";
function d3_geo_spherical(cartesian) {
return [
Math.atan2(cartesian[1], cartesian[0]),
Math.asin(Math.max(-1, Math.min(1, cartesian[2])))
d3_asin(cartesian[2])
];
}

Просмотреть файл

@ -147,14 +147,17 @@ d3.geom.quadtree = function(points, x1, y1, x2, y2) {
return arguments.length ? (y = _, quadtree) : y;
};
quadtree.extent = function(_) {
if (!arguments.length) return x1 == null ? null : [[x1, y1], [x2, y2]];
if (_ == null) x1 = y1 = x2 = y2 = null;
else x1 = +_[0][0], y1 = +_[0][1], x2 = +_[1][0], y2 = +_[1][1];
return quadtree;
};
quadtree.size = function(_) {
if (!arguments.length) return x1 == null ? null : [x2, y2];
if (_ == null) {
x1 = y1 = x2 = y2 = null;
} else {
x1 = y1 = 0;
x2 = +_[0], y2 = +_[1];
}
if (!arguments.length) return x1 == null ? null : [x2 - x1, y2 - y1];
if (_ == null) x1 = y1 = x2 = y2 = null;
else x1 = y1 = 0, x2 = +_[0], y2 = +_[1];
return quadtree;
};

Просмотреть файл

@ -27,10 +27,9 @@ import "polygon";
* @returns polygons [[[x1, y1], [x2, y2], ], ]
*/
d3.geom.voronoi = function(points) {
var size = null,
x = d3_svg_lineX,
var x = d3_svg_lineX,
y = d3_svg_lineY,
clip;
clipPolygon = null;
// For backwards-compatibility.
if (arguments.length) return voronoi(points);
@ -46,8 +45,8 @@ d3.geom.voronoi = function(points) {
Z = 1e6;
if (fx === d3_svg_lineX && fy === d3_svg_lineY) points = data;
else for (points = [], i = 0; i < n; ++i) {
points.push([+fx.call(this, d = data[i], i), +fy.call(this, d, i)]);
else for (points = new Array(n), i = 0; i < n; ++i) {
points[i] = [+fx.call(this, d = data[i], i), +fy.call(this, d, i)];
}
d3_geom_voronoiTessellate(points, function(e) {
@ -124,7 +123,7 @@ d3.geom.voronoi = function(points) {
}
});
if (clip) for (i = 0; i < n; ++i) clip(polygons[i]);
if (clipPolygon) for (i = 0; i < n; ++i) clipPolygon.clip(polygons[i]);
for (i = 0; i < n; ++i) polygons[i].point = data[i];
return polygons;
@ -138,17 +137,22 @@ d3.geom.voronoi = function(points) {
return arguments.length ? (y = _, voronoi) : y;
};
voronoi.size = function(_) {
if (!arguments.length) return size;
if (_ == null) {
clip = null;
} else {
size = [+_[0], +_[1]];
clip = d3.geom.polygon([[0, 0], [0, size[1]], size, [size[0], 0]]).clip;
voronoi.clipExtent = function(_) {
if (!arguments.length) return clipPolygon && [clipPolygon[0], clipPolygon[2]];
if (_ == null) clipPolygon = null;
else {
var x1 = +_[0][0], y1 = +_[0][1], x2 = +_[1][0], y2 = +_[1][1];
clipPolygon = d3.geom.polygon([[x1, y1], [x1, y2], [x2, y2], [x2, y1]]);
}
return voronoi;
};
// @deprecated; use clipExtent instead
voronoi.size = function(_) {
if (!arguments.length) return clipPolygon && clipPolygon[2];
return voronoi.clipExtent(_ && [[0, 0], _]);
};
voronoi.links = function(data) {
var points,
graph = data.map(function() { return []; }),
@ -160,8 +164,8 @@ d3.geom.voronoi = function(points) {
n = data.length;
if (fx === d3_svg_lineX && fy === d3_svg_lineY) points = data;
else for (i = 0; i < n; ++i) {
points.push([+fx.call(this, d = data[i], i), +fy.call(this, d, i)]);
else for (points = new Array(n), i = 0; i < n; ++i) {
points[i] = [+fx.call(this, d = data[i], i), +fy.call(this, d, i)];
}
d3_geom_voronoiTessellate(points, function(e) {
@ -178,18 +182,15 @@ d3.geom.voronoi = function(points) {
voronoi.triangles = function(data) {
if (x === d3_svg_lineX && y === d3_svg_lineY) return d3.geom.delaunay(data);
var points,
point,
var points = new Array(n),
fx = d3_functor(x),
fy = d3_functor(y),
d,
i,
n;
i = -1,
n = data.length;
for (i = 0, points = [], n = data.length; i < n; ++i) {
point = [+fx.call(this, d = data[i], i), +fy.call(this, d, i)];
point.data = d;
points.push(point);
while (++i < n) {
(points[i] = [+fx.call(this, d = data[i], i), +fy.call(this, d, i)]).data = d;
}
return d3.geom.delaunay(points).map(function(triangle) {

Просмотреть файл

@ -1,5 +1,6 @@
import "../arrays/range";
import "../math/trigonometry";
import "layout";
d3.layout.chord = function() {
var chord = {},

Просмотреть файл

@ -8,7 +8,8 @@ import "tree";
d3.layout.cluster = function() {
var hierarchy = d3.layout.hierarchy().sort(null).value(null),
separation = d3_layout_treeSeparation,
size = [1, 1]; // width, height
size = [1, 1], // width, height
nodeSize = false;
function cluster(d, i) {
var nodes = hierarchy.call(this, d, i),
@ -36,7 +37,10 @@ d3.layout.cluster = function() {
x1 = right.x + separation(right, left) / 2;
// Second walk, normalizing x & y to the desired size.
d3_layout_treeVisitAfter(root, function(node) {
d3_layout_treeVisitAfter(root, nodeSize ? function(node) {
node.x = (node.x - root.x) * size[0];
node.y = (root.y - node.y) * size[1];
} : function(node) {
node.x = (node.x - x0) / (x1 - x0) * size[0];
node.y = (1 - (root.y ? node.y / root.y : 1)) * size[1];
});
@ -51,8 +55,14 @@ d3.layout.cluster = function() {
};
cluster.size = function(x) {
if (!arguments.length) return size;
size = x;
if (!arguments.length) return nodeSize ? null : size;
nodeSize = (size = x) == null;
return cluster;
};
cluster.nodeSize = function(x) {
if (!arguments.length) return nodeSize ? size : null;
nodeSize = (size = x) != null;
return cluster;
};

Просмотреть файл

@ -5,41 +5,44 @@ import "tree";
d3.layout.pack = function() {
var hierarchy = d3.layout.hierarchy().sort(d3_layout_packSort),
padding = 0,
size = [1, 1];
size = [1, 1],
radius;
function pack(d, i) {
var nodes = hierarchy.call(this, d, i),
root = nodes[0];
root = nodes[0],
w = size[0],
h = size[1],
r = radius || Math.sqrt;
// Recursively compute the layout.
root.x = 0;
root.y = 0;
d3_layout_treeVisitAfter(root, function(d) { d.r = Math.sqrt(d.value); });
root.x = root.y = 0;
d3_layout_treeVisitAfter(root, function(d) { d.r = r(d.value); });
d3_layout_treeVisitAfter(root, d3_layout_packSiblings);
// Compute the scale factor the initial layout.
var w = size[0],
h = size[1],
k = Math.max(2 * root.r / w, 2 * root.r / h);
// When padding, recompute the layout using scaled padding.
if (padding > 0) {
var dr = padding * k / 2;
if (padding) {
var dr = padding * (radius ? 1 : Math.max(2 * root.r / w, 2 * root.r / h)) / 2;
d3_layout_treeVisitAfter(root, function(d) { d.r += dr; });
d3_layout_treeVisitAfter(root, d3_layout_packSiblings);
d3_layout_treeVisitAfter(root, function(d) { d.r -= dr; });
k = Math.max(2 * root.r / w, 2 * root.r / h);
}
// Scale the layout to fit the requested size.
d3_layout_packTransform(root, w / 2, h / 2, 1 / k);
// Translate and scale the layout to fit the requested size.
d3_layout_packTransform(root, w / 2, h / 2, radius ? 1 : 1 / Math.max(2 * root.r / w, 2 * root.r / h));
return nodes;
}
pack.size = function(x) {
pack.size = function(_) {
if (!arguments.length) return size;
size = x;
size = _;
return pack;
};
pack.radius = function(_) {
if (!arguments.length) return radius;
radius = _;
return pack;
};
@ -73,7 +76,7 @@ function d3_layout_packIntersects(a, b) {
var dx = b.x - a.x,
dy = b.y - a.y,
dr = a.r + b.r;
return dr * dr - dx * dx - dy * dy > .001; // within epsilon
return .999 * dr * dr > dx * dx + dy * dy; // relative error within epsilon
}
function d3_layout_packSiblings(node) {

Просмотреть файл

@ -5,7 +5,8 @@ import "hierarchy";
d3.layout.tree = function() {
var hierarchy = d3.layout.hierarchy().sort(null).value(null),
separation = d3_layout_treeSeparation,
size = [1, 1]; // width, height
size = [1, 1], // width, height
nodeSize = false;
function tree(d, i) {
var nodes = hierarchy.call(this, d, i),
@ -119,7 +120,11 @@ d3.layout.tree = function() {
y1 = deep.depth || 1;
// Clear temporary layout variables; transform x and y.
d3_layout_treeVisitAfter(root, function(node) {
d3_layout_treeVisitAfter(root, nodeSize ? function(node) {
node.x *= size[0];
node.y = node.depth * size[1];
delete node._tree;
} : function(node) {
node.x = (node.x - x0) / (x1 - x0) * size[0];
node.y = node.depth / y1 * size[1];
delete node._tree;
@ -135,8 +140,14 @@ d3.layout.tree = function() {
};
tree.size = function(x) {
if (!arguments.length) return size;
size = x;
if (!arguments.length) return nodeSize ? null : size;
nodeSize = (size = x) == null;
return tree;
};
tree.nodeSize = function(x) {
if (!arguments.length) return nodeSize ? size : null;
nodeSize = (size = x) != null;
return tree;
};

34
src/math/adder.js Normal file
Просмотреть файл

@ -0,0 +1,34 @@
// Adds floating point numbers with twice the normal precision.
// Reference: J. R. Shewchuk, Adaptive Precision Floating-Point Arithmetic and
// Fast Robust Geometric Predicates, Discrete & Computational Geometry 18(3)
// 305–363 (1997).
// Code adapted from GeographicLib by Charles F. F. Karney,
// http://geographiclib.sourceforge.net/
// See lib/geographiclib/LICENSE for details.
function d3_adder() {}
d3_adder.prototype = {
s: 0, // rounded value
t: 0, // exact error
add: function(y) {
d3_adderSum(y, this.t, d3_adderTemp);
d3_adderSum(d3_adderTemp.s, this.s, this);
if (this.s) this.t += d3_adderTemp.t;
else this.s = d3_adderTemp.t;
},
reset: function() {
this.s = this.t = 0;
},
valueOf: function() {
return this.s;
}
};
var d3_adderTemp = new d3_adder;
function d3_adderSum(a, b, o) {
var x = o.s = a + b, // a + b
bv = x - a, av = x - bv; // b_virtual & a_virtual
o.t = (a - av) + (b - bv); // a_roundoff + b_roundoff
}

Просмотреть файл

@ -1,5 +1,6 @@
var π = Math.PI,
ε = 1e-6,
ε2 = ε * ε,
d3_radians = π / 180,
d3_degrees = 180 / π;
@ -8,7 +9,7 @@ function d3_sgn(x) {
}
function d3_acos(x) {
return Math.acos(Math.max(-1, Math.min(1, x)));
return x > 1 ? 0 : x < -1 ? π : Math.acos(x);
}
function d3_asin(x) {

Просмотреть файл

@ -70,8 +70,8 @@ function d3_scale_linear(domain, range, interpolate, clamp) {
return d3_scale_linearTickFormat(domain, m, format);
};
scale.nice = function() {
d3_scale_nice(domain, d3_scale_linearNice);
scale.nice = function(m) {
d3_scale_linearNice(domain, m);
return rescale();
};
@ -86,12 +86,16 @@ function d3_scale_linearRebind(scale, linear) {
return d3.rebind(scale, linear, "range", "rangeRound", "interpolate", "clamp");
}
function d3_scale_linearNice(dx) {
dx = Math.pow(10, Math.round(Math.log(dx) / Math.LN10) - 1);
return dx && {
floor: function(x) { return Math.floor(x / dx) * dx; },
ceil: function(x) { return Math.ceil(x / dx) * dx; }
};
function d3_scale_linearNice(domain, m) {
return d3_scale_nice(domain, d3_scale_niceStep(m
? d3_scale_linearTickRange(domain, m)[2]
: d3_scale_linearNiceStep(domain)));
}
function d3_scale_linearNiceStep(domain) {
var extent = d3_scaleExtent(domain),
span = extent[1] - extent[0];
return Math.pow(10, Math.round(Math.log(span) / Math.LN10) - 1);
}
function d3_scale_linearTickRange(domain, m) {

Просмотреть файл

@ -32,7 +32,19 @@ function d3_scale_log(linear, base, log, pow, domain) {
};
scale.nice = function() {
linear.domain(d3_scale_nice(domain, nice).map(log));
function floor(x) {
return Math.pow(base, Math.floor(Math.log(x) / Math.log(base)));
}
function ceil(x) {
return Math.pow(base, Math.ceil(Math.log(x) / Math.log(base)));
}
linear.domain(d3_scale_nice(domain, log === d3_scale_logp
? {floor: floor, ceil: ceil}
: {floor: function(x) { return -ceil(-x); }, ceil: function(x) { return -floor(-x); }}).map(log));
return scale;
};
@ -61,8 +73,9 @@ function d3_scale_log(linear, base, log, pow, domain) {
};
scale.tickFormat = function(n, format) {
if (!arguments.length) return d3_scale_logFormat;
if (arguments.length < 2) format = d3_scale_logFormat;
if (!arguments.length) return format;
else if (typeof format !== "function") format = d3.format(format);
var b = Math.log(base),
k = Math.max(.1, n / scale.ticks().length),
f = log === d3_scale_logn ? (e = -1e-12, Math.floor) : (e = 1e-12, Math.ceil),
@ -76,20 +89,6 @@ function d3_scale_log(linear, base, log, pow, domain) {
return d3_scale_log(linear.copy(), base, log, pow, domain);
};
function nice() {
return log === d3_scale_logp
? {floor: floor, ceil: ceil}
: {floor: function(x) { return -ceil(-x); }, ceil: function(x) { return -floor(-x); }};
}
function floor(x) {
return Math.pow(base, Math.floor(Math.log(x) / Math.log(base)));
}
function ceil(x) {
return Math.pow(base, Math.ceil(Math.log(x) / Math.log(base)));
}
return d3_scale_linearRebind(scale, linear);
}

Просмотреть файл

@ -10,10 +10,19 @@ function d3_scale_nice(domain, nice) {
dx = x0, x0 = x1, x1 = dx;
}
if (nice = nice(x1 - x0)) {
domain[i0] = nice.floor(x0);
domain[i1] = nice.ceil(x1);
}
domain[i0] = nice.floor(x0);
domain[i1] = nice.ceil(x1);
return domain;
}
function d3_scale_niceStep(step) {
return step ? {
floor: function(x) { return Math.floor(x / step) * step; },
ceil: function(x) { return Math.ceil(x / step) * step; }
} : d3_scale_niceIdentity;
}
var d3_scale_niceIdentity = {
floor: d3_identity,
ceil: d3_identity
};

Просмотреть файл

@ -32,8 +32,8 @@ function d3_scale_pow(linear, exponent, domain) {
return d3_scale_linearTickFormat(domain, m, format);
};
scale.nice = function() {
return scale.domain(d3_scale_nice(domain, d3_scale_linearNice));
scale.nice = function(m) {
return scale.domain(d3_scale_linearNice(domain, m));
};
scale.exponent = function(x) {

Просмотреть файл

@ -34,5 +34,11 @@ function d3_scale_quantize(x0, x1, range) {
return d3_scale_quantize(x0, x1, range); // copy on write
};
scale.invertExtent = function(y) {
y = range.indexOf(y);
y = y < 0 ? NaN : y / kx + x0;
return [y, y + 1 / kx];
};
return rescale();
}

Просмотреть файл

@ -23,6 +23,11 @@ function d3_scale_threshold(domain, range) {
return scale;
};
scale.invertExtent = function(y) {
y = range.indexOf(y);
return [domain[y - 1], domain[y]];
};
scale.copy = function() {
return d3_scale_threshold(domain, range);
};

Просмотреть файл

@ -15,5 +15,7 @@ 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.call = d3_selectionPrototype.call;
d3_selection_enterPrototype.size = d3_selectionPrototype.size;
import "enter-select";

Просмотреть файл

@ -1,5 +1,6 @@
import "../core/array";
import "../core/document";
import "../core/vendor";
function d3_selection(groups) {
d3_arraySubclass(groups, d3_selectionPrototype);
@ -8,8 +9,7 @@ function d3_selection(groups) {
var d3_select = function(s, n) { return n.querySelector(s); },
d3_selectAll = function(s, n) { return n.querySelectorAll(s); },
d3_selectRoot = d3_document.documentElement,
d3_selectMatcher = d3_selectRoot.matchesSelector || d3_selectRoot.webkitMatchesSelector || d3_selectRoot.mozMatchesSelector || d3_selectRoot.msMatchesSelector || d3_selectRoot.oMatchesSelector,
d3_selectMatcher = d3_documentElement[d3_vendorSymbol(d3_documentElement, "matchesSelector")],
d3_selectMatches = function(n, s) { return d3_selectMatcher.call(n, s); };
// Prefer Sizzle, if available.
@ -46,20 +46,21 @@ import "each";
import "call";
import "empty";
import "node";
import "size";
import "enter";
import "transition";
// TODO fast singleton implementation?
d3.select = function(node) {
var group = [typeof node === "string" ? d3_select(node, d3_document) : node];
group.parentNode = d3_selectRoot;
group.parentNode = d3_documentElement;
return d3_selection([group]);
};
d3.selectAll = function(nodes) {
var group = d3_array(typeof nodes === "string" ? d3_selectAll(nodes, d3_document) : nodes);
group.parentNode = d3_selectRoot;
group.parentNode = d3_documentElement;
return d3_selection([group]);
};
var d3_selectionRoot = d3.select(d3_selectRoot);
var d3_selectionRoot = d3.select(d3_documentElement);

7
src/selection/size.js Normal file
Просмотреть файл

@ -0,0 +1,7 @@
import "selection";
d3_selectionPrototype.size = function() {
var n = 0;
this.each(function() { ++n; });
return n;
};

Просмотреть файл

@ -1,2 +1,2 @@
d3 = (function(){
var d3 = {version: "3.1.10"}; // semver
var d3 = {version: "3.2.0"}; // semver

Просмотреть файл

@ -33,7 +33,7 @@ d3.svg.axis = function() {
// Major ticks.
var tick = g.selectAll(".tick.major").data(ticks, String),
tickEnter = tick.enter().insert("g", "path").attr("class", "tick major").style("opacity", 1e-6),
tickEnter = tick.enter().insert("g", ".domain").attr("class", "tick major").style("opacity", 1e-6),
tickExit = d3.transition(tick.exit()).style("opacity", 1e-6).remove(),
tickUpdate = d3.transition(tick).style("opacity", 1),
tickTransform;

Просмотреть файл

@ -14,6 +14,7 @@ d3.svg.brush = function() {
y = null, // y-scale, optional
resizes = d3_svg_brushResizes[0],
extent = [[0, 0], [0, 0]], // [x0, y0], [x1, y1], in pixels (integers)
clamp = [true, true], // whether or not to clamp the extent to the range
extentDomain; // the extent in data space, lazily created
function brush(g) {
@ -223,8 +224,8 @@ d3.svg.brush = function() {
r1 -= size + position;
}
// Clamp the point so that the extent fits within the range extent.
min = Math.max(r0, Math.min(r1, point[i]));
// Clamp the point (unless clamp set to false) so that the extent fits within the range extent.
min = clamp[i] ? Math.max(r0, Math.min(r1, point[i])) : point[i];
// Compute the new extent bounds.
if (dragging) {
@ -285,6 +286,13 @@ d3.svg.brush = function() {
return brush;
};
brush.clamp = function(z) {
if (!arguments.length) return x && y ? clamp : clamp[+!x];
if (x && y) clamp = [!!z[0], !!z[1]];
else clamp[+!x] = !!z;
return brush;
};
brush.extent = function(z) {
var x0, x1, y0, y1, t;

Просмотреть файл

@ -91,6 +91,7 @@ function d3_svg_lineY(d) {
var d3_svg_lineInterpolators = d3.map({
"linear": d3_svg_lineLinear,
"linear-closed": d3_svg_lineLinearClosed,
"step": d3_svg_lineStep,
"step-before": d3_svg_lineStepBefore,
"step-after": d3_svg_lineStepAfter,
"basis": d3_svg_lineBasis,
@ -117,6 +118,17 @@ function d3_svg_lineLinearClosed(points) {
return d3_svg_lineLinear(points) + "Z";
}
// Step interpolation; generates "H" and "V" commands.
function d3_svg_lineStep(points) {
var i = 0,
n = points.length,
p = points[0],
path = [p[0], ",", p[1]];
while (++i < n) path.push("H", (p[0] + (p = points[i])[0]) / 2, "V", p[1]);
if (n > 1) path.push("H", p[0]);
return path.join("");
}
// Step interpolation; generates "H" and "V" commands.
function d3_svg_lineStepBefore(points) {
var i = 0,

Просмотреть файл

@ -3,7 +3,7 @@ import "time";
import "year";
d3.time.day = d3_time_interval(function(date) {
var day = new d3_time(1970, 0);
var day = new d3_time(2000, 0);
day.setFullYear(date.getFullYear(), date.getMonth(), date.getDate());
return day;
}, function(date, offset) {

Просмотреть файл

@ -36,8 +36,14 @@ d3.time.format = function(template) {
// The am-pm flag is 0 for AM, and 1 for PM.
if ("p" in d) d.H = d.H % 12 + d.p * 12;
var date = new d3_time();
date.setFullYear(d.y, d.m, d.d);
var date = new d3_time;
if ("j" in d) date.setFullYear(d.y, 0, d.j);
else if ("w" in d && ("W" in d || "U" in d)) {
date.setFullYear(d.y, 0, 1);
date.setFullYear(d.y, 0, "W" in d
? (d.w + 6) % 7 + d.W * 7 - (date.getDay() + 5) % 7
: d.w + d.U * 7 - (date.getDay() + 6) % 7);
} else date.setFullYear(d.y, d.m, d.d);
date.setHours(d.H, d.M, d.S, d.L);
return date;
};
@ -79,17 +85,21 @@ function d3_time_formatLookup(names) {
}
function d3_time_formatPad(value, fill, width) {
value += "";
var length = value.length;
return length < width ? new Array(width - length + 1).join(fill) + value : value;
var sign = value < 0 ? "-" : "",
string = (sign ? -value : value) + "",
length = string.length;
return sign + (length < width ? new Array(width - length + 1).join(fill) + string : string);
}
var d3_time_dayRe = d3_time_formatRe(d3_time_days),
d3_time_dayLookup = d3_time_formatLookup(d3_time_days),
d3_time_dayAbbrevRe = d3_time_formatRe(d3_time_dayAbbreviations),
d3_time_dayAbbrevLookup = d3_time_formatLookup(d3_time_dayAbbreviations),
d3_time_monthRe = d3_time_formatRe(d3_time_months),
d3_time_monthLookup = d3_time_formatLookup(d3_time_months),
d3_time_monthAbbrevRe = d3_time_formatRe(d3_time_monthAbbreviations),
d3_time_monthAbbrevLookup = d3_time_formatLookup(d3_time_monthAbbreviations);
d3_time_monthAbbrevLookup = d3_time_formatLookup(d3_time_monthAbbreviations),
d3_time_percentRe = /^%/;
var d3_time_formatPads = {
"-": "",
@ -134,48 +144,63 @@ var d3_time_parsers = {
e: d3_time_parseDay,
H: d3_time_parseHour24,
I: d3_time_parseHour24,
// j: function(d, s, i) { /*TODO day of year [001,366] */ return i; },
j: d3_time_parseDayOfYear,
L: d3_time_parseMilliseconds,
m: d3_time_parseMonthNumber,
M: d3_time_parseMinutes,
p: d3_time_parseAmPm,
S: d3_time_parseSeconds,
// U: function(d, s, i) { /*TODO week number (sunday) [00,53] */ return i; },
// w: function(d, s, i) { /*TODO weekday [0,6] */ return i; },
// W: function(d, s, i) { /*TODO week number (monday) [00,53] */ return i; },
U: d3_time_parseWeekNumberSunday,
w: d3_time_parseWeekdayNumber,
W: d3_time_parseWeekNumberMonday,
x: d3_time_parseLocaleDate,
X: d3_time_parseLocaleTime,
y: d3_time_parseYear,
Y: d3_time_parseFullYear
// ,
Y: d3_time_parseFullYear,
// Z: function(d, s, i) { /*TODO time zone */ return i; },
// "%": function(d, s, i) { /*TODO literal % */ return i; }
"%": d3_time_parseLiteralPercent
};
// Note: weekday is validated, but does not set the date.
function d3_time_parseWeekdayAbbrev(date, string, i) {
d3_time_dayAbbrevRe.lastIndex = 0;
var n = d3_time_dayAbbrevRe.exec(string.substring(i));
return n ? i += n[0].length : -1;
return n ? (date.w = d3_time_dayAbbrevLookup.get(n[0].toLowerCase()), i + n[0].length) : -1;
}
// Note: weekday is validated, but does not set the date.
function d3_time_parseWeekday(date, string, i) {
d3_time_dayRe.lastIndex = 0;
var n = d3_time_dayRe.exec(string.substring(i));
return n ? i += n[0].length : -1;
return n ? (date.w = d3_time_dayLookup.get(n[0].toLowerCase()), i + n[0].length) : -1;
}
function d3_time_parseWeekdayNumber(date, string, i) {
d3_time_numberRe.lastIndex = 0;
var n = d3_time_numberRe.exec(string.substring(i, i + 1));
return n ? (date.w = +n[0], i + n[0].length) : -1;
}
function d3_time_parseWeekNumberSunday(date, string, i) {
d3_time_numberRe.lastIndex = 0;
var n = d3_time_numberRe.exec(string.substring(i));
return n ? (date.U = +n[0], i + n[0].length) : -1;
}
function d3_time_parseWeekNumberMonday(date, string, i) {
d3_time_numberRe.lastIndex = 0;
var n = d3_time_numberRe.exec(string.substring(i));
return n ? (date.W = +n[0], i + n[0].length) : -1;
}
function d3_time_parseMonthAbbrev(date, string, i) {
d3_time_monthAbbrevRe.lastIndex = 0;
var n = d3_time_monthAbbrevRe.exec(string.substring(i));
return n ? (date.m = d3_time_monthAbbrevLookup.get(n[0].toLowerCase()), i += n[0].length) : -1;
return n ? (date.m = d3_time_monthAbbrevLookup.get(n[0].toLowerCase()), i + n[0].length) : -1;
}
function d3_time_parseMonth(date, string, i) {
d3_time_monthRe.lastIndex = 0;
var n = d3_time_monthRe.exec(string.substring(i));
return n ? (date.m = d3_time_monthLookup.get(n[0].toLowerCase()), i += n[0].length) : -1;
return n ? (date.m = d3_time_monthLookup.get(n[0].toLowerCase()), i + n[0].length) : -1;
}
function d3_time_parseLocaleFull(date, string, i) {
@ -193,13 +218,13 @@ function d3_time_parseLocaleTime(date, string, i) {
function d3_time_parseFullYear(date, string, i) {
d3_time_numberRe.lastIndex = 0;
var n = d3_time_numberRe.exec(string.substring(i, i + 4));
return n ? (date.y = +n[0], i += n[0].length) : -1;
return n ? (date.y = +n[0], i + n[0].length) : -1;
}
function d3_time_parseYear(date, string, i) {
d3_time_numberRe.lastIndex = 0;
var n = d3_time_numberRe.exec(string.substring(i, i + 2));
return n ? (date.y = d3_time_expandYear(+n[0]), i += n[0].length) : -1;
return n ? (date.y = d3_time_expandYear(+n[0]), i + n[0].length) : -1;
}
function d3_time_expandYear(d) {
@ -209,38 +234,44 @@ function d3_time_expandYear(d) {
function d3_time_parseMonthNumber(date, string, i) {
d3_time_numberRe.lastIndex = 0;
var n = d3_time_numberRe.exec(string.substring(i, i + 2));
return n ? (date.m = n[0] - 1, i += n[0].length) : -1;
return n ? (date.m = n[0] - 1, i + n[0].length) : -1;
}
function d3_time_parseDay(date, string, i) {
d3_time_numberRe.lastIndex = 0;
var n = d3_time_numberRe.exec(string.substring(i, i + 2));
return n ? (date.d = +n[0], i += n[0].length) : -1;
return n ? (date.d = +n[0], i + n[0].length) : -1;
}
function d3_time_parseDayOfYear(date, string, i) {
d3_time_numberRe.lastIndex = 0;
var n = d3_time_numberRe.exec(string.substring(i, i + 3));
return n ? (date.j = +n[0], i + n[0].length) : -1;
}
// Note: we don't validate that the hour is in the range [0,23] or [1,12].
function d3_time_parseHour24(date, string, i) {
d3_time_numberRe.lastIndex = 0;
var n = d3_time_numberRe.exec(string.substring(i, i + 2));
return n ? (date.H = +n[0], i += n[0].length) : -1;
return n ? (date.H = +n[0], i + n[0].length) : -1;
}
function d3_time_parseMinutes(date, string, i) {
d3_time_numberRe.lastIndex = 0;
var n = d3_time_numberRe.exec(string.substring(i, i + 2));
return n ? (date.M = +n[0], i += n[0].length) : -1;
return n ? (date.M = +n[0], i + n[0].length) : -1;
}
function d3_time_parseSeconds(date, string, i) {
d3_time_numberRe.lastIndex = 0;
var n = d3_time_numberRe.exec(string.substring(i, i + 2));
return n ? (date.S = +n[0], i += n[0].length) : -1;
return n ? (date.S = +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.L = +n[0], i += n[0].length) : -1;
return n ? (date.L = +n[0], i + n[0].length) : -1;
}
// Note: we don't look at the next directive.
@ -264,3 +295,9 @@ function d3_time_zone(d) {
zm = Math.abs(z) % 60;
return zs + d3_time_formatPad(zh, "0", 2) + d3_time_formatPad(zm, "0", 2);
}
function d3_time_parseLiteralPercent(date, string, i) {
d3_time_percentRe.lastIndex = 0;
var n = d3_time_percentRe.exec(string.substring(i, i + 1));
return n ? i + n[0].length : -1;
}

Просмотреть файл

@ -30,7 +30,7 @@ function d3_time_scale(linear, methods, format) {
};
scale.nice = function(m) {
return scale.domain(d3_scale_nice(scale.domain(), function() { return m; }));
return scale.domain(d3_scale_nice(scale.domain(), m));
};
scale.ticks = function(m, k) {

Просмотреть файл

@ -17,7 +17,13 @@ function d3_xhr(url, mimeType, response, callback) {
var xhr = {},
dispatch = d3.dispatch("progress", "load", "error"),
headers = {},
request = new (d3_window.XDomainRequest && /^(http(s)?:)?\/\//.test(url) ? XDomainRequest : XMLHttpRequest);
request = new XMLHttpRequest,
responseType = null;
// If IE does not support CORS, use XDomainRequest.
if (d3_window.XDomainRequest
&& !("withCredentials" in request)
&& /^(http(s)?:)?\/\//.test(url)) request = new XDomainRequest;
"onload" in request
? request.onload = request.onerror = respond
@ -60,6 +66,14 @@ function d3_xhr(url, mimeType, response, callback) {
return xhr;
};
// Specifies what type the response value should take;
// for instance, arraybuffer, blob, document, or text.
xhr.responseType = function(value) {
if (!arguments.length) return responseType;
responseType = value;
return xhr;
};
// Specify how to convert the response content to a specific type;
// changes the callback value on "load" events.
xhr.response = function(value) {
@ -81,6 +95,7 @@ function d3_xhr(url, mimeType, response, callback) {
if (mimeType != null && !("accept" in headers)) headers["accept"] = mimeType + ",*/*";
if (request.setRequestHeader) for (var name in headers) request.setRequestHeader(name, headers[name]);
if (mimeType != null && request.overrideMimeType) request.overrideMimeType(mimeType);
if (responseType != null) request.responseType = responseType;
if (callback != null) xhr.on("error", callback).on("load", function(request) { callback(null, request); });
request.send(data == null ? null : data);
return xhr;

Просмотреть файл

@ -31,6 +31,18 @@ suite.addBatch({
[-64.66070178517852, 18.33986913231323]
]]}), 4.890516e-13, 1e-13);
},
"zero area": function(area) {
assert.equal(area({
"type": "Polygon",
"coordinates": [[
[96.79142432523281, 5.262704519048153],
[96.81065389253769, 5.272455576551362],
[96.82988345984256, 5.272455576551362],
[96.81065389253769, 5.272455576551362],
[96.79142432523281, 5.262704519048153]
]]
}), 0);
},
"semilune": function(area) {
assert.inDelta(area({type: "Polygon", coordinates: [[[0, 0], [0, 90], [90, 0], [0, 0]]]}), π / 2, 1e-6);
},

Просмотреть файл

@ -10,6 +10,26 @@ var formatNumber = d3.format(",.02r"),
o,
then;
o = circle();
then = Date.now();
for (var i = 0, k = 0; i < n * 1000; i++, k++) {
path(o);
}
console.log("Single circle: " + formatNumber((Date.now() - then) / k) + "ms/op.");
o = JSON.parse(fs.readFileSync("./test/data/us-counties.json")).features;
then = Date.now();
for (var i = 0, k = 0; i < n; i++, k++) {
for (var j = 0, m = o.length; j < m; ++j) {
path(o[j]);
}
}
console.log("U.S. counties (separate): " + formatNumber((Date.now() - then) / k) + "ms/op.");
o = JSON.parse(fs.readFileSync("./test/data/us-counties.json"));
then = Date.now();

Просмотреть файл

@ -228,6 +228,36 @@ suite.addBatch({
}
]
}), [[-120,46], [-119,47]]);
},
"null geometries": {
"Feature": function(bounds) {
var b = bounds({type: "Feature", geometry: null});
assert.isNaN(b[0][0]);
assert.isNaN(b[0][1]);
assert.isNaN(b[1][0]);
assert.isNaN(b[1][1]);
},
"MultiPoint": function(bounds) {
var b = bounds({type: "MultiPoint", coordinates: []});
assert.isNaN(b[0][0]);
assert.isNaN(b[0][1]);
assert.isNaN(b[1][0]);
assert.isNaN(b[1][1]);
},
"MultiLineString": function(bounds) {
var b = bounds({type: "MultiLineString", coordinates: []});
assert.isNaN(b[0][0]);
assert.isNaN(b[0][1]);
assert.isNaN(b[1][0]);
assert.isNaN(b[1][1]);
},
"MultiPolygon": function(bounds) {
var b = bounds({type: "MultiPolygon", coordinates: []});
assert.isNaN(b[0][0]);
assert.isNaN(b[0][1]);
assert.isNaN(b[1][0]);
assert.isNaN(b[1][1]);
}
}
}
});

Просмотреть файл

@ -8,26 +8,31 @@ var suite = vows.describe("d3.geo.centroid");
suite.addBatch({
"centroid": {
topic: load("geo/centroid").expression("d3.geo.centroid"),
"Point": function(centroid) {
assert.deepEqual(centroid({type: "Point", coordinates: [0, 0]}), [0, 0]);
"the centroid of a point is itself": function(centroid) {
assert.inDelta(centroid({type: "Point", coordinates: [0, 0]}), [0, 0], 1e-6);
assert.inDelta(centroid({type: "Point", coordinates: [1, 1]}), [1, 1], 1e-6);
assert.inDelta(centroid({type: "Point", coordinates: [2, 3]}), [2, 3], 1e-6);
assert.inDelta(centroid({type: "Point", coordinates: [-4, -5]}), [-4, -5], 1e-6);
},
"MultiPoint": {
"": function(centroid) {
assert.inDelta(centroid({type: "MultiPoint", coordinates: [[0, 0], [1, 2]]}), [0.499847, 1.000038], 1e-6);
},
"antimeridian": function(centroid) {
assert.deepEqual(centroid({type: "MultiPoint", coordinates: [[179, 0], [-179, 0]]}), [180, 0]);
},
"rings": {
"equator": function(centroid) {
assert.isUndefined(centroid({type: "MultiPoint", coordinates: [[0, 0], [90, 0], [180, 0], [-90, 0]]}));
},
"polar": function(centroid) {
assert.isUndefined(centroid({type: "MultiPoint", coordinates: [[0, 0], [0, 90], [180, 0], [0, -90]]}));
}
}
"the centroid of a set of points is the (spherical) average of its constituent members": function(centroid) {
assert.inDelta(centroid({type: "GeometryCollection", geometries: [{type: "Point", coordinates: [0, 0]}, {type: "Point", coordinates: [1, 2]}]}), [0.499847, 1.000038], 1e-6);
assert.inDelta(centroid({type: "MultiPoint", coordinates: [[0, 0], [1, 2]]}), [0.499847, 1.000038], 1e-6);
assert.inDelta(centroid({type: "MultiPoint", coordinates: [[179, 0], [-179, 0]]}), [180, 0], 1e-6);
},
"LineString": function(centroid) {
"the centroid of a set of points and their antipodes is ambiguous": function(centroid) {
assert.ok(centroid({type: "MultiPoint", coordinates: [[0, 0], [180, 0]]}).every(isNaN));
assert.ok(centroid({type: "MultiPoint", coordinates: [[0, 0], [90, 0], [180, 0], [-90, 0]]}).every(isNaN));
assert.ok(centroid({type: "MultiPoint", coordinates: [[0, 0], [0, 90], [180, 0], [0, -90]]}).every(isNaN));
},
"the centroid of the empty set of points is ambiguous": function(centroid) {
assert.ok(centroid({type: "MultiPoint", coordinates: []}).every(isNaN));
},
"the centroid of a line string is the (spherical) average of its constituent great arc segments": function(centroid) {
assert.inDelta(centroid({type: "LineString", coordinates: [[0, 0], [1, 0]]}), [.5, 0], 1e-6);
assert.inDelta(centroid({type: "LineString", coordinates: [[0, 0], [0, 90]]}), [0, 45], 1e-6);
assert.inDelta(centroid({type: "LineString", coordinates: [[0, 0], [0, 45], [0, 90]]}), [0, 45], 1e-6);
@ -36,65 +41,145 @@ suite.addBatch({
assert.inDelta(centroid({type: "LineString", coordinates: [[179, -1], [-179, 1]]}), [180, 0], 1e-6);
assert.inDelta(centroid({type: "LineString", coordinates: [[-179, 0], [0, 0], [179, 0]]}), [0, 0], 1e-6);
assert.inDelta(centroid({type: "LineString", coordinates: [[-180, -90], [0, 0], [0, 90]]}), [0, 0], 1e-6);
assert.isUndefined(centroid({type: "LineString", coordinates: [[0, -90], [0, 90]]}));
},
"MultiLineString": function(centroid) {
"the centroid of a great arc from a point to its antipode is ambiguous": function(centroid) {
assert.ok(centroid({type: "LineString", coordinates: [[180, 0], [0, 0]]}).every(isNaN));
assert.ok(centroid({type: "MultiLineString", coordinates: [[[0, -90], [0, 90]]]}).every(isNaN));
},
"the centroid of a set of line strings is the (spherical) average of its constituent great arc segments": function(centroid) {
assert.inDelta(centroid({type: "MultiLineString", coordinates: [[[0, 0], [0, 2]]]}), [0, 1], 1e-6);
},
"Polygon": function(centroid) {
"a line of zero length is treated as points": function(centroid) {
assert.inDelta(centroid({type: "LineString", coordinates: [[1, 1], [1, 1]]}), [1, 1], 1e-6);
assert.inDelta(centroid({type: "GeometryCollection", geometries: [{type: "Point", coordinates: [0, 0]}, {type: "LineString", coordinates: [[1, 2], [1, 2]]}]}), [0.666534, 1.333408], 1e-6);
},
"an empty polygon with non-zero extent is treated as a line": function(centroid) {
assert.inDelta(centroid({type: "Polygon", coordinates: [[[1, 1], [2, 1], [3, 1], [2, 1], [1, 1]]]}), [2, 1.000076], 1e-6);
assert.inDelta(centroid({type: "GeometryCollection", geometries: [{type: "Point", coordinates: [0, 0]}, {type: "Polygon", coordinates: [[[1, 2], [1, 2], [1, 2], [1, 2]]]}]}), [0.799907, 1.600077], 1e-6);
},
"an empty polygon with zero extent is treated as a point": function(centroid) {
assert.inDelta(centroid({type: "Polygon", coordinates: [[[1, 1], [1, 1], [1, 1], [1, 1]]]}), [1, 1], 1e-6);
assert.inDelta(centroid({type: "GeometryCollection", geometries: [{type: "Point", coordinates: [0, 0]}, {type: "Polygon", coordinates: [[[1, 2], [1, 2], [1, 2], [1, 2]]]}]}), [0.799907, 1.600077], 1e-6);
},
"the centroid of the equator is ambiguous": function(centroid) {
assert.ok(centroid({type: "LineString", coordinates: [[0, 0], [120, 0], [-120, 0], [0, 0]]}).every(isNaN));
},
"the centroid of a polygon is the (spherical) average of its surface": function(centroid) {
assert.inDelta(centroid({type: "Polygon", coordinates: [[[0, -90], [0, 0], [0, 90], [1, 0], [0, -90]]]}), [.5, 0], 1e-6);
assert.inDelta(centroid(_.geo.circle().angle(5).origin([0, 45])()), [0, 45], 1e-6);
assert.equal(centroid({type: "Polygon", coordinates: [_.range(-180, 180 + 1 / 2, 1).map(function(x) { return [x, -60]; })]})[1], -90);
assert.inDelta(centroid({type: "Polygon", coordinates: [_.range(-180, 180 + 1 / 2, 1).map(function(x) { return [x, -60]; })]})[1], -90, 1e-6);
assert.inDelta(centroid({type: "Polygon", coordinates: [[[0, -10], [0, 10], [10, 10], [10, -10], [0, -10]]]}), [5, 0], 1e-6);
},
"MultiPolygon": function(centroid) {
assert.inDelta(centroid({type: "MultiPolygon", coordinates: [[[[0, -90], [0, 0], [0, 90], [1, 0], [0, -90]]]]}), [.5, 0], 1e-6);
"the centroid of a set of polygons is the (spherical) average of its surface": function(centroid) {
var circle = _.geo.circle();
assert.inDelta(centroid({
type: "MultiPolygon",
coordinates: [
circle.angle(45).origin([0, 0])().coordinates,
circle.angle(60).origin([180, 0])().coordinates
]
}), [180, 0], 1e-6);
},
"Sphere": function(centroid) {
assert.isUndefined(centroid({type: "Sphere"}));
"the centroid of a lune is the (spherical) average of its surface": function(centroid) {
assert.inDelta(centroid({type: "Polygon", coordinates: [[[0, -90], [0, 0], [0, 90], [1, 0], [0, -90]]]}), [.5, 0], 1e-6);
},
"Feature": function(centroid) {
assert.deepEqual(centroid({type: "Feature", geometry: {type: "Point", coordinates: [0, 0]}}), [0, 0]);
"the centroid of a small circle is its origin": {
"5°": function(centroid) {
assert.inDelta(centroid(_.geo.circle().angle(5).origin([30, 45])()), [30, 45], 1e-6);
},
"135°": function(centroid) {
assert.inDelta(centroid(_.geo.circle().angle(135).origin([30, 45])()), [30, 45], 1e-6);
},
"South Pole": function(centroid) {
assert.equal(centroid({type: "Polygon", coordinates: [_.range(-180, 180 + 1 / 2, 1).map(function(x) { return [x, -60]; })]})[1], -90);
},
"equator": function(centroid) {
assert.inDelta(centroid({type: "Polygon", coordinates: [[[0, -10], [0, 10], [10, 10], [10, -10], [0, -10]]]}), [5, 0], 1e-6);
},
"equator with coincident points": function(centroid) {
assert.inDelta(centroid({type: "Polygon", coordinates: [[[0, -10], [0, 10], [0, 10], [10, 10], [10, -10], [0, -10]]]}), [5, 0], 1e-6);
},
"other": function(centroid) {
assert.inDelta(centroid({type: "Polygon", coordinates: [[[-180, 0], [-180, 10], [-179, 10], [-179, 0], [-180, 0]]]}), [-179.5, 4.987448], 1e-6);
},
"concentric rings": function(centroid) {
var circle = _.geo.circle().origin([0, 45]),
coordinates = circle.angle(60)().coordinates;
coordinates.push(circle.angle(45)().coordinates[0].reverse());
assert.inDelta(centroid({type: "Polygon", coordinates: coordinates}), [0, 45], 1e-6);
}
},
"FeatureCollection": function(centroid) {
"the centroid of a spherical square on the equator": function(centroid) {
assert.inDelta(centroid({type: "Polygon", coordinates: [[[0, -10], [0, 10], [10, 10], [10, -10], [0, -10]]]}), [5, 0], 1e-6);
},
"the centroid of a spherical square touching the antimeridian": function(centroid) {
assert.inDelta(centroid({type: "Polygon", coordinates: [[[-180, 0], [-180, 10], [-179, 10], [-179, 0], [-180, 0]]]}), [-179.5, 4.987448], 1e-6);
},
"concentric rings": function(centroid) {
var circle = _.geo.circle().origin([0, 45]),
coordinates = circle.angle(60)().coordinates;
coordinates.push(circle.angle(45)().coordinates[0].reverse());
assert.inDelta(centroid({type: "Polygon", coordinates: coordinates}), [0, 45], 1e-6);
},
"the centroid of a sphere is ambiguous": function(centroid) {
assert.ok(centroid({type: "Sphere"}).every(isNaN));
},
"the centroid of a feature is the centroid of its constituent geometry": function(centroid) {
assert.inDelta(centroid({type: "Feature", geometry: {type: "LineString", coordinates: [[1, 1], [1, 1]]}}), [1, 1], 1e-6);
assert.inDelta(centroid({type: "Feature", geometry: {type: "Point", coordinates: [1, 1]}}), [1, 1], 1e-6);
assert.inDelta(centroid({type: "Feature", geometry: {type: "Polygon", coordinates: [[[0, -90], [0, 0], [0, 90], [1, 0], [0, -90]]]}}), [.5, 0], 1e-6);
},
"the centroid of a feature collection is the centroid of its constituent geometry": function(centroid) {
assert.inDelta(centroid({type: "FeatureCollection", features: [
{type: "Feature", geometry: {type: "LineString", coordinates: [[179, 0], [180, 0]]}},
{type: "Feature", geometry: {type: "Point", coordinates: [0, 0]}}
]}), [179.5, 0], 1e-6);
},
"GeometryCollection": {
"LineString, Point": function(centroid) {
assert.inDelta(centroid({type: "GeometryCollection", geometries: [
{type: "LineString", coordinates: [[179, 0], [180, 0]]},
{type: "Point", coordinates: [0, 0]}
]}), [179.5, 0], 1e-6);
},
"Polygon, LineString, Point": function(centroid) {
assert.inDelta(centroid({type: "GeometryCollection", geometries: [
{type: "Polygon", coordinates: [[[-180, 0], [-180, 1], [-179, 1], [-179, 0], [-180, 0]]]},
{type: "LineString", coordinates: [[179, 0], [180, 0]]},
{type: "Point", coordinates: [0, 0]}
]}), [-179.5, 0.5], 1e-6);
},
"Point, LineString, Polygon": function(centroid) {
assert.inDelta(centroid({type: "GeometryCollection", geometries: [
{type: "Point", coordinates: [0, 0]},
{type: "LineString", coordinates: [[179, 0], [180, 0]]},
{type: "Polygon", coordinates: [[[-180, 0], [-180, 1], [-179, 1], [-179, 0], [-180, 0]]]}
]}), [-179.5, 0.5], 1e-6);
},
"Sphere, Point": function(centroid) {
assert.isUndefined(centroid({type: "GeometryCollection", geometries: [
{type: "Sphere"},
{type: "Point", coordinates: [0, 0]}
]}));
},
"Point, Sphere": function(centroid) {
assert.isUndefined(centroid({type: "GeometryCollection", geometries: [
{type: "Point", coordinates: [0, 0]},
{type: "Sphere"}
]}));
}
"the centroid of a non-empty line string and a point only considers the line string": function(centroid) {
assert.inDelta(centroid({type: "GeometryCollection", geometries: [
{type: "LineString", coordinates: [[179, 0], [180, 0]]},
{type: "Point", coordinates: [0, 0]}
]}), [179.5, 0], 1e-6);
},
"the centroid of a non-empty polygon, a non-empty line string and a point only considers the polygon": function(centroid) {
assert.inDelta(centroid({type: "GeometryCollection", geometries: [
{type: "Polygon", coordinates: [[[-180, 0], [-180, 1], [-179, 1], [-179, 0], [-180, 0]]]},
{type: "LineString", coordinates: [[179, 0], [180, 0]]},
{type: "Point", coordinates: [0, 0]}
]}), [-179.5, 0.500006], 1e-6);
assert.inDelta(centroid({type: "GeometryCollection", geometries: [
{type: "Point", coordinates: [0, 0]},
{type: "LineString", coordinates: [[179, 0], [180, 0]]},
{type: "Polygon", coordinates: [[[-180, 0], [-180, 1], [-179, 1], [-179, 0], [-180, 0]]]}
]}), [-179.5, 0.500006], 1e-6);
},
"the centroid of the sphere and a point is the point": function(centroid) {
assert.deepEqual(centroid({type: "GeometryCollection", geometries: [
{type: "Sphere"},
{type: "Point", coordinates: [0, 0]}
]}), [0, 0]);
assert.deepEqual(centroid({type: "GeometryCollection", geometries: [
{type: "Point", coordinates: [0, 0]},
{type: "Sphere"}
]}), [0, 0]);
}
}
});

Просмотреть файл

@ -130,7 +130,7 @@ suite.addBatch({
assert.deepEqual(centroid({type: "Point", coordinates: [0, 0]}), [480, 250]);
},
"of an empty multipoint": function(centroid) {
assert.isUndefined(centroid({type: "MultiPoint", coordinates: []}));
assert.ok(centroid({type: "MultiPoint", coordinates: []}).every(isNaN));
},
"of a singleton multipoint": function(centroid) {
assert.deepEqual(centroid({type: "MultiPoint", coordinates: [[0, 0]]}), [480, 250]);
@ -139,15 +139,15 @@ suite.addBatch({
assert.deepEqual(centroid({type: "MultiPoint", coordinates: [[-122, 37], [-74, 40]]}), [-10, 57.5]);
},
"of an empty linestring": function(centroid) {
assert.isUndefined(centroid({type: "LineString", coordinates: []}));
assert.ok(centroid({type: "LineString", coordinates: []}).every(isNaN));
},
"of a linestring with two points": function(centroid) {
assert.deepEqual(centroid({type: "LineString", coordinates: [[100, 0], [0, 0]]}), [730, 250]);
assert.deepEqual(centroid({type: "LineString", coordinates: [[0, 0], [100, 0], [101, 0]]}), [732.5, 250]);
},
"of a linestring with two points, one unique": function(centroid) {
assert.isUndefined(centroid({type: "LineString", coordinates: [[-122, 37], [-122, 37]]}));
assert.isUndefined(centroid({type: "LineString", coordinates: [[ -74, 40], [ -74, 40]]}));
assert.deepEqual(centroid({type: "LineString", coordinates: [[-122, 37], [-122, 37]]}), [-130, 65]);
assert.deepEqual(centroid({type: "LineString", coordinates: [[-74, 40], [-74, 40]]}), [110, 50]);
},
"of a linestring with three points; two unique": function(centroid) {
assert.deepEqual(centroid({type: "LineString", coordinates: [[-122, 37], [-74, 40], [-74, 40]]}), [-10, 57.5]);
@ -162,7 +162,7 @@ suite.addBatch({
assert.deepEqual(centroid({type: "Polygon", coordinates: [[[100, 0], [100, 1], [101, 1], [101, 0], [100, 0]]]}), [982.5, 247.5]);
},
"of a zero-area polygon": function(centroid) {
assert.isUndefined(centroid({type: "Polygon", coordinates: [[[1, 0], [2, 0], [3, 0], [1, 0]]]}));
assert.deepEqual(centroid({type: "Polygon", coordinates: [[[1, 0], [2, 0], [3, 0], [1, 0]]]}), [490, 250]);
},
"of a polygon with two rings, one with zero area": function(centroid) {
assert.deepEqual(centroid({type: "Polygon", coordinates: [
@ -180,7 +180,7 @@ suite.addBatch({
}), [479.642857, 250], 1e-6);
},
"of an empty multipolygon": function(centroid) {
assert.isUndefined(centroid({type: "MultiPolygon", coordinates: []}));
assert.ok(centroid({type: "MultiPolygon", coordinates: []}).every(isNaN));
},
"of a singleton multipolygon": function(centroid) {
assert.deepEqual(centroid({type: "MultiPolygon", coordinates: [[[[100, 0], [100, 1], [101, 1], [101, 0], [100, 0]]]]}), [982.5, 247.5]);
@ -294,7 +294,7 @@ suite.addBatch({
[[109.378, 189.584], [797.758, 504.660]], 1e-3);
},
"centroid of a line string": function(p) {
assert.inDelta(p.centroid({type: "LineString", coordinates: [[-122, 37], [-74, 40], [-100, 0]]}), [545.130, 253.859], 1e-3);
assert.inDelta(p.centroid({type: "LineString", coordinates: [[-122, 37], [-74, 40], [-100, 0]]}), [545.131, 253.860], 1e-3);
}
},
@ -757,19 +757,38 @@ suite.addBatch({
},
"with an Albers projection and adaptive resampling": {
topic: function(path) {
return path()
"correctly resamples near the poles": function(path) {
var p = path()
.context(testContext)
.projection(_.geo.albers()
.scale(140)
.rotate([0, 0])
.precision(1));
},
"correctly resamples near the poles": function(p) {
p({type: "LineString", coordinates: [[0, 88], [180, 89]]});
assert.isTrue(testContext.buffer().filter(function(d) { return d.type === "lineTo"; }).length > 1);
p({type: "LineString", coordinates: [[180, 90], [1, 89.5]]});
assert.isTrue(testContext.buffer().filter(function(d) { return d.type === "lineTo"; }).length > 1);
},
"rotate([11.5, 285])": function(path) {
var p = path()
.context(testContext)
.projection(_.geo.albers()
.scale(140)
.rotate([11.5, 285])
.precision(1));
p({type: "LineString", coordinates: [[170, 20], [170, 0]]});
assert.isTrue(testContext.buffer().filter(function(d) { return d.type === "lineTo"; }).length > 1);
},
"wavy projection": function(path) {
var p = path()
.context(testContext)
.projection(_.geo.projection(function(λ, φ) {
return [λ, Math.sin(λ * 4)];
})
.scale(140)
.precision(1));
p({type: "LineString", coordinates: [[-45, 0], [45, 0]]});
assert.isTrue(testContext.buffer().filter(function(d) { return d.type === "lineTo"; }).length > 1);
}
},

Просмотреть файл

@ -0,0 +1,18 @@
import "../../src/geo/point-in-polygon";
import "../../src/math/trigonometry";
d3.geo.pointInPolygon = function(polygon) {
polygon = polygon.map(function(ring) {
ring = ring.map(pointRadians);
ring.pop();
return ring;
});
return function(point) {
return d3_geo_pointInPolygon(pointRadians(point), polygon);
};
function pointRadians(point) {
return [point[0] * d3_radians, point[1] * d3_radians];
}
};

Просмотреть файл

@ -0,0 +1,352 @@
var vows = require("vows"),
_ = require("../../"),
load = require("../load"),
assert = require("../assert");
var suite = vows.describe("d3.geo.pointInPolygon");
suite.addBatch({
"d3.geo.pointInPolygon": {
topic: load("../test/geo/point-in-polygon-mock").expression("d3.geo.pointInPolygon"),
"empty": function(pointInPolygon) {
assert.ok(!pointInPolygon([])([0, 0]));
},
"simple": {
topic: function(pointInPolygon) {
return pointInPolygon([[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]]);
},
"outside": function(pointInPolygon) {
assert.ok(!pointInPolygon([.1, 2]));
},
"inside": function(pointInPolygon) {
assert.ok(pointInPolygon([.1, .1]));
}
},
"small circle": {
topic: function(pointInPolygon) {
return pointInPolygon(_.geo.circle().angle(60)().coordinates);
},
"outside": function(pointInPolygon) {
assert.ok(!pointInPolygon([-180, 0]));
},
"inside": function(pointInPolygon) {
assert.ok(pointInPolygon([1, 1]));
}
},
"South pole": {
topic: function(pointInPolygon) {
return pointInPolygon([
[[-60, -80], [60, -80], [180, -80], [-60, -80]]
]);
},
"outside": function(pointInPolygon) {
assert.ok(!pointInPolygon([0, 0]));
},
"inside": function(pointInPolygon) {
assert.ok(pointInPolygon([0, -85]));
}
},
"North pole": {
topic: function(pointInPolygon) {
return pointInPolygon([
[[60, 80], [-60, 80], [-180, 80], [60, 80]]
]);
},
"outside": function(pointInPolygon) {
assert.ok(!pointInPolygon([0, 0]));
},
"inside": function(pointInPolygon) {
assert.ok(pointInPolygon([0, 85]));
}
},
"larger than hemisphere": {
"near [0°, 0°]": {
topic: function(pointInPolygon) {
return pointInPolygon([
[[0, 0], [1, 0], [1, 1], [0, 1], [0, 0]]
]);
},
"outside": function(pointInPolygon) {
assert.ok(!pointInPolygon([.1, .1]));
},
"inside": function(pointInPolygon) {
assert.ok(pointInPolygon([2, .1]));
}
},
"South pole": {
topic: function(pointInPolygon) {
return pointInPolygon([
[[-60, 80], [60, 80], [180, 80], [-60, 80]]
]);
},
"outside": function(pointInPolygon) {
assert.ok(!pointInPolygon([0, 85]));
},
"inside": function(pointInPolygon) {
assert.ok(pointInPolygon([0, 0]));
}
},
"North pole": {
topic: function(pointInPolygon) {
return pointInPolygon([
[[60, -80], [-60, -80], [-180, -80], [60, -80]]
]);
},
"outside": function(pointInPolygon) {
assert.ok(!pointInPolygon([0, -85]));
},
"inside": function(pointInPolygon) {
assert.ok(pointInPolygon([0, 0]));
}
},
"circle with radius 120°": {
topic: function(pointInPolygon) {
return pointInPolygon(_.geo.circle().angle(120)().coordinates);
},
"outside": function(pointInPolygon) {
assert.ok(!pointInPolygon([-180, 0]));
},
"inside": function(pointInPolygon) {
assert.ok(pointInPolygon([-90, 0]));
}
},
"narrow strip hole, length 340°": {
topic: function(pointInPolygon) {
return pointInPolygon([
[[-170, -1], [0, -1], [170, -1], [170, 1], [0, 1], [-170, 1], [-170, -1]]
]);
},
"outside": function(pointInPolygon) {
assert.ok(!pointInPolygon([0, 0]));
},
"inside": function(pointInPolygon) {
assert.ok(pointInPolygon([0, 20]));
}
}
},
"ring": {
"near [0°, 0°]": {
topic: function(pointInPolygon) {
return pointInPolygon([
[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]],
[[.4, .4], [.6, .4], [.6, .6], [.4, .6], [.4, .4]]
]);
},
"outside": function(pointInPolygon) {
assert.ok(!pointInPolygon([.5, .5]));
},
"inside": function(pointInPolygon) {
assert.ok(pointInPolygon([.1, .5]));
}
},
"equatorial": {
topic: function(pointInPolygon) {
return pointInPolygon([
[[0, -10], [-120, -10], [120, -10], [0, -10]],
[[0, 10], [120, 10], [-120, 10], [0, 10]]
]);
},
"outside": function(pointInPolygon) {
assert.ok(!pointInPolygon([0, 20]));
},
"inside": function(pointInPolygon) {
assert.ok(pointInPolygon([0, 0]));
}
},
"excluding both poles": {
topic: function(pointInPolygon) {
return pointInPolygon([
[[10, 10], [-10, 10], [-10, -10], [10, -10], [10, 10]].reverse(),
[[170, 10], [170, -10], [-170, -10], [-170, 10], [170, 10]].reverse()
]);
},
"outside": function(pointInPolygon) {
assert.ok(!pointInPolygon([0, 90]));
},
"inside": function(pointInPolygon) {
assert.ok(pointInPolygon([0, 0]));
}
},
"containing both poles": {
topic: function(pointInPolygon) {
return pointInPolygon([
[[10, 10], [-10, 10], [-10, -10], [10, -10], [10, 10]],
[[170, 10], [170, -10], [-170, -10], [-170, 10], [170, 10]]
]);
},
"outside": function(pointInPolygon) {
assert.ok(!pointInPolygon([0, 0]));
},
"inside": function(pointInPolygon) {
assert.ok(pointInPolygon([0, 20]));
}
},
"containing the South pole": {
topic: function(pointInPolygon) {
return pointInPolygon([
[[10, 10], [-10, 10], [-10, -10], [10, -10], [10, 10]],
[[0, 80], [120, 80], [-120, 80], [0, 80]]
]);
},
"outside": function(pointInPolygon) {
assert.ok(!pointInPolygon([0, 90]));
},
"inside": function(pointInPolygon) {
assert.ok(pointInPolygon([0, -90]));
}
},
"containing the North pole": {
topic: function(pointInPolygon) {
return pointInPolygon([
[[10, 10], [-10, 10], [-10, -10], [10, -10], [10, 10]].reverse(),
[[0, 80], [120, 80], [-120, 80], [0, 80]].reverse()
]);
},
"inside": function(pointInPolygon) {
assert.ok(pointInPolygon([0, 90]));
},
"outside": function(pointInPolygon) {
assert.ok(!pointInPolygon([0, -90]));
}
}
},
"self-intersecting": {
"near [0°, 0°]": {
topic: function(pointInPolygon) {
return pointInPolygon([
[[0, 0], [1, 0], [1, 3], [3, 3], [3, 1], [0, 1], [0, 0]]
]);
},
"inside": {
"counter-clockwise region": function(pointInPolygon) {
assert.ok(pointInPolygon([.5, .5]));
},
"clockwise region": function(pointInPolygon) {
assert.ok(pointInPolygon([2, 2]));
}
},
"outside": {
"counter-clockwise region": function(pointInPolygon) {
assert.ok(!pointInPolygon([15, .5]));
},
"clockwise region": function(pointInPolygon) {
assert.ok(!pointInPolygon([12, 2]));
}
}
},
"near the South pole": {
topic: function(pointInPolygon) {
return pointInPolygon([
[[-10, -80], [120, -80], [-120, -80], [10, -85], [10, -75], [-10, -75], [-10, -80]]
]);
},
"inside": {
"counter-clockwise region": function(pointInPolygon) {
assert.ok(pointInPolygon([0, -76]));
},
"clockwise region": function(pointInPolygon) {
assert.ok(pointInPolygon([0, -89]));
}
},
"outside": function(pointInPolygon) {
assert.ok(!pointInPolygon([0, 0]));
}
},
"near the North pole": {
topic: function(pointInPolygon) {
return pointInPolygon([
[[-10, 80], [-10, 75], [10, 75], [10, 85], [-120, 80], [120, 80], [-10, 80]]
]);
},
"inside": {
"clockwise region": function(pointInPolygon) {
assert.ok(pointInPolygon([0, 76]));
},
"counter-clockwise region": function(pointInPolygon) {
assert.ok(pointInPolygon([0, 89]));
}
},
"outside": function(pointInPolygon) {
assert.ok(!pointInPolygon([0, 0]));
}
}
},
"touching a pole": {
"hemisphere touching the South pole": {
topic: function(pointInPolygon) {
return pointInPolygon(_.geo.circle().angle(90)().coordinates);
},
"origin is inside": function(pointInPolygon) {
assert.ok(pointInPolygon([0, 0]));
}
},
"triangle touching the South pole": {
topic: function(pointInPolygon) {
return pointInPolygon([[[180, -90], [-45, 0], [45, 0], [180, -90]]]);
},
"inside": function(pointInPolygon) {
assert.ok(pointInPolygon([-44, 0]));
assert.ok(pointInPolygon([0, 0]));
assert.ok(pointInPolygon([0, -30]));
assert.ok(pointInPolygon([30, -80]));
},
"outside": function(pointInPolygon) {
assert.ok(!pointInPolygon([-46, 0]));
assert.ok(!pointInPolygon([0, 1]));
assert.ok(!pointInPolygon([-90, -80]));
}
},
"triangle touching the South pole (2)": {
topic: function(pointInPolygon) {
return pointInPolygon([[[-45, 0], [45, 0], [180, -90], [-45, 0]]]);
},
"inside": function(pointInPolygon) {
assert.ok(pointInPolygon([-44, 0]));
assert.ok(pointInPolygon([0, 0]));
assert.ok(pointInPolygon([0, -30]));
assert.ok(pointInPolygon([30, -80]));
},
"outside": function(pointInPolygon) {
assert.ok(!pointInPolygon([-46, 0]));
assert.ok(!pointInPolygon([0, 1]));
assert.ok(!pointInPolygon([-90, -80]));
}
},
"triangle touching the South pole (3)": {
topic: function(pointInPolygon) {
return pointInPolygon([[[180, -90], [-135, 0], [135, 0], [180, -90]]]);
},
"inside": function(pointInPolygon) {
assert.ok(pointInPolygon([180, 0]));
assert.ok(pointInPolygon([150, 0]));
assert.ok(pointInPolygon([180, -30]));
assert.ok(pointInPolygon([150, -80]));
},
"outside": function(pointInPolygon) {
assert.ok(!pointInPolygon([0, 0]));
assert.ok(!pointInPolygon([180, 1]));
assert.ok(!pointInPolygon([-90, -80]));
}
},
"triangle touching the North pole": {
topic: function(pointInPolygon) {
return pointInPolygon([[[180, 90], [45, 0], [-45, 0], [180, 90]]]);
},
"inside": function(pointInPolygon) {
assert.ok(pointInPolygon([-44, 10]));
assert.ok(pointInPolygon([0, 10]));
assert.ok(pointInPolygon([30, 80]));
},
"outside": function(pointInPolygon) {
assert.ok(!pointInPolygon([-90, 0]));
assert.ok(!pointInPolygon([0, -1]));
assert.ok(!pointInPolygon([0, -80]));
assert.ok(!pointInPolygon([-90, 1]));
assert.ok(!pointInPolygon([-90, 80]));
}
}
}
}
});
suite.export(module);

Просмотреть файл

@ -14,6 +14,9 @@ suite.addBatch({
"has no defined size": function(q) {
assert.isNull(q.size());
},
"has no defined extent": function(q) {
assert.isNull(q.extent());
},
"has the default x-accessor, d[0]": function(q) {
assert.strictEqual(q.x()([42, 43]), 42);
},

Просмотреть файл

@ -12,16 +12,40 @@ suite.addBatch({
topic: function(voronoi) {
return voronoi();
},
"has no defined clip extent": function(v) {
assert.isNull(v.clipExtent());
},
"has no defined size": function(v) {
assert.isNull(v.size());
},
"returns the configured clip extent": function(v) {
try {
assert.deepEqual(v.clipExtent([[1, 2], [3, 4]]).clipExtent(), [[1, 2], [3, 4]]);
} finally {
v.clipExtent(null);
}
},
"returns the configured size": function(v) {
try {
assert.deepEqual(v.size([100, 100]).size(), [100, 100]);
assert.deepEqual(v.size([1, 2]).size(), [1, 2]);
} finally {
v.size(null);
}
},
"size implies a clip extent from [0, 0]": function(v) {
try {
assert.deepEqual(v.size([1, 2]).clipExtent(), [[0, 0], [1, 2]]);
} finally {
v.size(null);
}
},
"clip extent implies a size, assuming [0, 0]": function(v) {
try {
assert.deepEqual(v.clipExtent([[1, 2], [3, 4]]).size(), [3, 4]);
} finally {
v.clipExtent(null);
}
},
"has the default x-accessor, d[0]": function(v) {
assert.strictEqual(v.x()([42, 43]), 42);
},
@ -113,15 +137,32 @@ suite.addBatch({
[[480, -1e6], [480, 1e6], [1e6, -1e6], [1e6, 1e6]]
], 1e-6);
}
},
"links": {
topic: function(v) {
return v.y(function(d) { return d.y; });
},
"for two points": function(v) {
assert.deepEqual(v.links([{x: 200, y: 200}, {x: 760, y: 300}]), [
{source: {x: 200, y: 200}, target: {x: 760, y: 300}}
]);
},
"for three points": function(v) {
assert.deepEqual(v.links([{x: 200, y: 200}, {x: 500, y: 250}, {x: 760, y: 300}]), [
{source: {x: 200, y: 200}, target: {x: 760, y: 300}},
{source: {x: 500, y: 250}, target: {x: 760, y: 300}},
{source: {x: 200, y: 200}, target: {x: 500, y: 250}}
]);
}
}
},
"a voronoi layout with size 960x500": {
"a voronoi layout with clip extent [[0, 0], [960, 500]]": {
topic: function(voronoi) {
return voronoi()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; })
.size([960, 500]);
.clipExtent([[0, 0], [960, 500]]);
},
"of two points": {
topic: function(v) {

164
test/layout/chord-test.js Normal file
Просмотреть файл

@ -0,0 +1,164 @@
var vows = require("vows"),
load = require("../load"),
assert = require("../assert");
var suite = vows.describe("d3.layout.chord");
suite.addBatch({
"chord": {
topic: load("layout/chord").expression("d3.layout.chord"),
"of a simple matrix": {
topic: function(chord) {
return chord()
.padding(.05)
.sortSubgroups(function(a, b) { return b - a; })
.matrix([
[11975, 5871, 8916, 2868],
[ 1951, 10048, 2060, 6171],
[ 8010, 16145, 8090, 8045],
[ 1013, 990, 940, 6907]
]);
},
"computes chord groups": function(chord) {
var groups = chord.groups();
assert.equal(groups.length, 4);
assert.equal(groups[0].index, 0);
assert.inDelta(groups[0].startAngle, 0.0000000000000000, 1e-6);
assert.inDelta(groups[0].endAngle, 1.8024478065173115, 1e-6);
assert.inDelta(groups[0].value, 29630, 1e-6);
assert.equal(groups[1].index, 1);
assert.inDelta(groups[1].startAngle, 1.8524478065173116, 1e-6);
assert.inDelta(groups[1].endAngle, 3.0830761941597418, 1e-6);
assert.inDelta(groups[1].value, 20230, 1e-6);
assert.equal(groups[2].index, 2);
assert.inDelta(groups[2].startAngle, 3.1330761941597416, 1e-6);
assert.inDelta(groups[2].endAngle, 5.583991554422396, 1e-6);
assert.inDelta(groups[2].value, 40290, 1e-6);
assert.equal(groups[3].index, 3);
assert.inDelta(groups[3].startAngle, 5.6339915544223960, 1e-6);
assert.inDelta(groups[3].endAngle, 6.233185307179585, 1e-6);
assert.inDelta(groups[3].value, 9850, 1e-6);
},
"computes chords": function(chord) {
var chords = chord.chords();
assert.equal(chords.length, 10);
assert.equal(chords[0].source.index, 0);
assert.equal(chords[0].source.subindex, 0);
assert.inDelta(chords[0].source.startAngle, 0, 1e-6);
assert.inDelta(chords[0].source.endAngle, 0.7284614405347555, 1e-6);
assert.equal(chords[0].source.value, 11975);
assert.equal(chords[0].target.index, 0);
assert.equal(chords[0].target.subindex, 0);
assert.inDelta(chords[0].target.startAngle, 0, 1e-6);
assert.inDelta(chords[0].target.endAngle, 0.7284614405347555, 1e-6);
assert.equal(chords[0].target.value, 11975);
assert.equal(chords[1].source.index, 0);
assert.equal(chords[1].source.subindex, 1);
assert.inDelta(chords[1].source.startAngle, 1.2708382425228875, 1e-6);
assert.inDelta(chords[1].source.endAngle, 1.6279820519074009, 1e-6);
assert.equal(chords[1].source.value, 5871);
assert.equal(chords[1].target.index, 1);
assert.equal(chords[1].target.subindex, 0);
assert.inDelta(chords[1].target.startAngle, 2.964393248816668, 1e-6);
assert.inDelta(chords[1].target.endAngle, 3.0830761941597418, 1e-6);
assert.equal(chords[1].target.value, 1951);
assert.equal(chords[2].source.index, 0);
assert.equal(chords[2].source.subindex, 2);
assert.inDelta(chords[2].source.startAngle, 0.7284614405347555, 1e-6);
assert.inDelta(chords[2].source.endAngle, 1.2708382425228875, 1e-6);
assert.equal(chords[2].source.value, 8916);
assert.equal(chords[2].target.index, 2);
assert.equal(chords[2].target.subindex, 0);
assert.inDelta(chords[2].target.startAngle, 5.0967284113173115, 1e-6);
assert.inDelta(chords[2].target.endAngle, 5.583991554422396, 1e-6);
assert.equal(chords[2].target.value, 8010);
assert.equal(chords[3].source.index, 0);
assert.equal(chords[3].source.subindex, 3);
assert.inDelta(chords[3].source.startAngle, 1.6279820519074009, 1e-6);
assert.inDelta(chords[3].source.endAngle, 1.8024478065173115, 1e-6);
assert.equal(chords[3].source.value, 2868);
assert.equal(chords[3].target.index, 3);
assert.equal(chords[3].target.subindex, 0);
assert.inDelta(chords[3].target.startAngle, 6.05415716358929, 1e-6);
assert.inDelta(chords[3].target.endAngle, 6.115779830751019, 1e-6);
assert.equal(chords[3].target.value, 1013);
assert.equal(chords[4].source.index, 1);
assert.equal(chords[4].source.subindex, 1);
assert.inDelta(chords[4].source.startAngle, 1.8524478065173116, 1e-6);
assert.inDelta(chords[4].source.endAngle, 2.4636862661827164, 1e-6);
assert.equal(chords[4].source.value, 10048);
assert.equal(chords[4].target.index, 1);
assert.equal(chords[4].target.subindex, 1);
assert.inDelta(chords[4].target.startAngle, 1.8524478065173116, 1e-6);
assert.inDelta(chords[4].target.endAngle, 2.4636862661827164, 1e-6);
assert.equal(chords[4].target.value, 10048);
assert.equal(chords[5].source.index, 2);
assert.equal(chords[5].source.subindex, 1);
assert.inDelta(chords[5].source.startAngle, 3.1330761941597416, 1e-6);
assert.inDelta(chords[5].source.endAngle, 4.1152064620038855, 1e-6);
assert.equal(chords[5].source.value, 16145);
assert.equal(chords[5].target.index, 1);
assert.equal(chords[5].target.subindex, 2);
assert.inDelta(chords[5].target.startAngle, 2.8390796314887687, 1e-6);
assert.inDelta(chords[5].target.endAngle, 2.964393248816668, 1e-6);
assert.equal(chords[5].target.value, 2060);
assert.equal(chords[6].source.index, 1);
assert.equal(chords[6].source.subindex, 3);
assert.inDelta(chords[6].source.startAngle, 2.4636862661827164, 1e-6);
assert.inDelta(chords[6].source.endAngle, 2.8390796314887687, 1e-6);
assert.equal(chords[6].source.value, 6171);
assert.equal(chords[6].target.index, 3);
assert.equal(chords[6].target.subindex, 1);
assert.inDelta(chords[6].target.startAngle, 6.115779830751019, 1e-6);
assert.inDelta(chords[6].target.endAngle, 6.176003365292097, 1e-6);
assert.equal(chords[6].target.value, 990);
assert.equal(chords[7].source.index, 2);
assert.equal(chords[7].source.subindex, 2);
assert.inDelta(chords[7].source.startAngle, 4.1152064620038855, 1e-6);
assert.inDelta(chords[7].source.endAngle, 4.607336153354714, 1e-6);
assert.equal(chords[7].source.value, 8090);
assert.equal(chords[7].target.index, 2);
assert.equal(chords[7].target.subindex, 2);
assert.inDelta(chords[7].target.startAngle, 4.1152064620038855, 1e-6);
assert.inDelta(chords[7].target.endAngle, 4.607336153354714, 1e-6);
assert.equal(chords[7].target.value, 8090);
assert.equal(chords[8].source.index, 2);
assert.equal(chords[8].source.subindex, 3);
assert.inDelta(chords[8].source.startAngle, 4.607336153354714, 1e-6);
assert.inDelta(chords[8].source.endAngle, 5.0967284113173115, 1e-6);
assert.equal(chords[8].source.value, 8045);
assert.equal(chords[8].target.index, 3);
assert.equal(chords[8].target.subindex, 2);
assert.inDelta(chords[8].target.startAngle, 6.176003365292097, 1e-6);
assert.inDelta(chords[8].target.endAngle, 6.233185307179585, 1e-6);
assert.equal(chords[8].target.value, 940);
assert.equal(chords[9].source.index, 3);
assert.equal(chords[9].source.subindex, 3);
assert.inDelta(chords[9].source.startAngle, 5.633991554422396, 1e-6);
assert.inDelta(chords[9].source.endAngle, 6.05415716358929, 1e-6);
assert.equal(chords[9].source.value, 6907);
assert.equal(chords[9].target.index, 3);
assert.equal(chords[9].target.subindex, 3);
assert.inDelta(chords[9].target.startAngle, 5.633991554422396, 1e-6);
assert.inDelta(chords[9].target.endAngle, 6.05415716358929, 1e-6);
assert.equal(chords[9].target.value, 6907);
}
}
}
});
suite.export(module);

Просмотреть файл

@ -77,6 +77,16 @@ suite.addBatch({
assert.isFalse(result.slice(1).some(function(d, i) {
return d.x === result[i].x && d.y === result[i].y && d.value > 0;
}));
},
"radius defaults to automatic scaling": function(pack) {
assert.equal(pack().radius(), null);
},
"radius can be specified using a custom function of value": function(pack) {
var p = pack().radius(function(value) { return Math.sqrt(value) * 10; });
assert.deepEqual(p.nodes({children: [{value: 1}]}).map(layout), [
{value: 1, depth: 0, x: 0.5, y: 0.5, r: 10},
{value: 1, depth: 1, x: 0.5, y: 0.5, r: 10}
]);
}
}
});

Просмотреть файл

@ -220,6 +220,14 @@ suite.addBatch({
assert.deepEqual(x.domain(), [1, 1, 2, 3, 11]);
var x = d3.scale.linear().domain([123.1, 1, 2, 3, -.9]).nice();
assert.deepEqual(x.domain(), [130, 1, 2, 3, -10]);
},
"accepts a tick count to control nicing step": function(d3) {
var x = d3.scale.linear().domain([12, 87]).nice(5);
assert.deepEqual(x.domain(), [0, 100]);
var x = d3.scale.linear().domain([12, 87]).nice(10);
assert.deepEqual(x.domain(), [10, 90]);
var x = d3.scale.linear().domain([12, 87]).nice(100);
assert.deepEqual(x.domain(), [12, 87]);
}
},

Просмотреть файл

@ -214,6 +214,15 @@ suite.addBatch({
"+100", "+200", "+300", "", "", "", "", "", "",
"+1,000"
]);
},
"can override the tick format as string": function(d3) {
var x = d3.scale.log().domain([1000.1, 1]);
assert.deepEqual(x.ticks().map(x.tickFormat(10, ".1s")), [
"1", "2", "3", "", "", "", "", "", "",
"10", "20", "30", "", "", "", "", "", "",
"100", "200", "300", "", "", "", "", "", "",
"1k"
]);
}
},

Просмотреть файл

@ -193,6 +193,14 @@ suite.addBatch({
assert.deepEqual(x.domain(), [1, 1, 2, 3, 11]);
var x = d3.scale.pow().domain([123.1, 1, 2, 3, -.9]).nice();
assert.deepEqual(x.domain(), [130, 1, 2, 3, -10]);
},
"accepts a tick count to control nicing step": function(d3) {
var x = d3.scale.pow().domain([12, 87]).nice(5);
assert.deepEqual(x.domain(), [0, 100]);
var x = d3.scale.pow().domain([12, 87]).nice(10);
assert.deepEqual(x.domain(), [10, 90]);
var x = d3.scale.pow().domain([12, 87]).nice(100);
assert.deepEqual(x.domain(), [12, 87]);
}
},

Просмотреть файл

@ -59,6 +59,32 @@ suite.addBatch({
assert.equal(x(.6), b);
assert.equal(x(.8), c);
assert.equal(x(1), c);
},
"invertExtent": {
"maps a value in the range to a domain extent": function(quantize) {
var x = quantize().range([0, 1, 2, 3]);
assert.deepEqual(x.invertExtent(0), [0, .25]);
assert.deepEqual(x.invertExtent(1), [.25, .5]);
assert.deepEqual(x.invertExtent(2), [.5, .75]);
assert.deepEqual(x.invertExtent(3), [.75, 1]);
},
"allows arbitrary range values": function(quantize) {
var a = {}, b = {}, x = quantize().range([a, b]);
assert.deepEqual(x.invertExtent(a), [0, .5]);
assert.deepEqual(x.invertExtent(b), [.5, 1]);
},
"returns [NaN, NaN] when the given value is not in the range": function(quantize) {
var x = quantize();
assert.ok(x.invertExtent(-1).every(isNaN));
assert.ok(x.invertExtent(.5).every(isNaN));
assert.ok(x.invertExtent(2).every(isNaN));
assert.ok(x.invertExtent('a').every(isNaN));
},
"returns the first match if duplicate values exist in the range": function(quantize) {
var x = quantize().range([0, 1, 2, 0]);
assert.deepEqual(x.invertExtent(0), [0, .25]);
assert.deepEqual(x.invertExtent(1), [.25, .5]);
}
}
}
});

Просмотреть файл

@ -42,6 +42,14 @@ suite.addBatch({
assert.equal(x(.6), b);
assert.equal(x(.8), c);
assert.equal(x(1), c);
},
"invertExtent": {
"returns the domain extent for the specified range value": function(threshold) {
var a = {}, b = {}, c = {}, x = threshold().domain([1/3, 2/3]).range([a, b, c]);
assert.deepEqual(x.invertExtent(a), [undefined, 1/3]);
assert.deepEqual(x.invertExtent(b), [1/3, 2/3]);
assert.deepEqual(x.invertExtent(c), [2/3, undefined]);
}
}
}
});

Просмотреть файл

@ -0,0 +1,52 @@
var vows = require("vows"),
load = require("../load"),
assert = require("../assert");
var suite = vows.describe("selection.size");
suite.addBatch({
"select(body)": {
topic: load("selection/size").document(),
"on a simple page": {
topic: function(d3) {
return d3.select("body");
},
"returns zero for empty selections": function(body) {
assert.equal(body.select("foo").size(), 0);
},
"returns one for a singleton selection": function(body) {
assert.equal(body.size(), 1);
},
"does not count null nodes": function(body) {
body[0][0] = null;
assert.equal(body.size(), 0);
}
}
}
});
suite.addBatch({
"selectAll(div)": {
topic: load("selection/node").document(),
"on a simple page": {
topic: function(d3) {
var body = d3.select("body");
body.append("div").append("span");
body.append("div");
return body.selectAll("div");
},
"returns null for empty selections": function(div) {
assert.equal(div.select("foo").size(), 0);
},
"returns the number of non-null nodes": function(div) {
assert.equal(div.size(), 2);
},
"does not count null nodes": function(div) {
div[0][0] = null;
assert.equal(div.size(), 1);
}
}
}
});
suite.export(module);

Просмотреть файл

@ -21,6 +21,35 @@ suite.addBatch({
}
},
"clamp": {
"returns a single boolean if only x is defined": function(brush) {
var b = brush().x(_.scale.linear());
assert.isTrue(b.clamp());
},
"returns a single boolean if only y is defined": function(brush) {
var b = brush().y(_.scale.linear());
assert.isTrue(b.clamp());
},
"returns one-dimensional array if both x and y are defined": function(brush) {
var b = brush().x(_.scale.linear()).y(_.scale.linear());
assert.deepEqual(b.clamp(), [true, true]);
},
"takes a single boolean if only x is defined": function(brush) {
var b = brush().x(_.scale.linear()).clamp(false);
assert.isFalse(b.clamp());
},
"takes a single boolean if only y is defined": function(brush) {
var b = brush().y(_.scale.linear()).clamp(false);
assert.isFalse(b.clamp());
},
"takes a one-dimensional array if both x and y are defined": function(brush) {
var b = brush().x(_.scale.linear()).y(_.scale.linear()).clamp([false, true]);
assert.deepEqual(b.clamp(), [false, true]);
b.clamp([true, false]);
assert.deepEqual(b.clamp(), [true, false]);
}
},
"extent": {
"returns null when no scales are attached": function(brush) {
assert.isNull(brush().extent());

Просмотреть файл

@ -93,6 +93,12 @@ suite.addBatch({
assert.pathEqual(l([[0, 0], [1, 1]]), "M0,0V1H1");
assert.pathEqual(l([[0, 0], [1, 1], [2, 0]]), "M0,0V1H1V0H2");
},
"supports step interpolation": function(line) {
var l = line().interpolate("step");
assert.pathEqual(l([[0, 0]]), "M0,0");
assert.pathEqual(l([[0, 0], [1, 1]]), "M0,0H0.5V1H1");
assert.pathEqual(l([[0, 0], [1, 1], [2, 0]]), "M0,0H0.5V1H1.5V0H2");
},
"supports step-after interpolation": function(line) {
var l = line().interpolate("step-after");
assert.pathEqual(l([[0, 0]]), "M0,0");

Просмотреть файл

@ -142,6 +142,7 @@ suite.addBatch({
var f = format("%y");
assert.equal(f(local(1990, 0, 1)), "90");
assert.equal(f(local(2002, 0, 1)), "02");
assert.equal(f(local(-2, 0, 1)), "-02");
},
"formats zero-padded four-digit year": function(format) {
var f = format("%Y");
@ -149,6 +150,7 @@ suite.addBatch({
assert.equal(f(local(1990, 0, 1)), "1990");
assert.equal(f(local(2002, 0, 1)), "2002");
assert.equal(f(local(10002, 0, 1)), "0002");
assert.equal(f(local(-2, 0, 1)), "-0002");
},
"formats time zone": function(format) {
var f = format("%Z");
@ -172,6 +174,48 @@ suite.addBatch({
assert.deepEqual(p("Wednesday 02/03/1991"), local(1991, 1, 3));
assert.isNull(p("Caturday 03/10/2010"));
},
"parses abbreviated weekday, week number (Sunday) and year": function(format) {
var p = format("%a %U %Y").parse;
assert.deepEqual(p("Mon 00 1990"), local(1990, 0, 1));
assert.deepEqual(p("Sun 05 1991"), local(1991, 1, 3));
assert.deepEqual(p("Sun 01 1995"), local(1995, 0, 1));
assert.isNull(p("XXX 03 2010"));
},
"parses weekday, week number (Sunday) and year": function(format) {
var p = format("%A %U %Y").parse;
assert.deepEqual(p("Monday 00 1990"), local(1990, 0, 1));
assert.deepEqual(p("Sunday 05 1991"), local(1991, 1, 3));
assert.deepEqual(p("Sunday 01 1995"), local(1995, 0, 1));
assert.isNull(p("Caturday 03 2010"));
},
"parses numeric weekday, week number (Sunday) and year": function(format) {
var p = format("%w %U %Y").parse;
assert.deepEqual(p("1 00 1990"), local(1990, 0, 1));
assert.deepEqual(p("0 05 1991"), local(1991, 1, 3));
assert.deepEqual(p("0 01 1995"), local(1995, 0, 1));
assert.isNull(p("X 03 2010"));
},
"parses abbreviated weekday, week number (Monday) and year": function(format) {
var p = format("%a %W %Y").parse;
assert.deepEqual(p("Mon 01 1990"), local(1990, 0, 1));
assert.deepEqual(p("Sun 04 1991"), local(1991, 1, 3));
assert.deepEqual(p("Sun 00 1995"), local(1995, 0, 1));
assert.isNull(p("XXX 03 2010"));
},
"parses weekday, week number (Monday) and year": function(format) {
var p = format("%A %W %Y").parse;
assert.deepEqual(p("Monday 01 1990"), local(1990, 0, 1));
assert.deepEqual(p("Sunday 04 1991"), local(1991, 1, 3));
assert.deepEqual(p("Sunday 00 1995"), local(1995, 0, 1));
assert.isNull(p("Caturday 03 2010"));
},
"parses numeric weekday, week number (Monday) and year": function(format) {
var p = format("%w %W %Y").parse;
assert.deepEqual(p("1 01 1990"), local(1990, 0, 1));
assert.deepEqual(p("0 04 1991"), local(1991, 1, 3));
assert.deepEqual(p("0 00 1995"), local(1995, 0, 1));
assert.isNull(p("X 03 2010"));
},
"parses numeric date": function(format) {
var p = format("%m/%d/%y").parse;
assert.deepEqual(p("01/01/90"), local(1990, 0, 1));
@ -196,6 +240,12 @@ suite.addBatch({
assert.deepEqual(p("February 2, 2010"), local(2010, 1, 2));
assert.isNull(p("jan 1, 1990"));
},
"parses day of year and numeric date": function(format) {
var p = format("%j %m/%d/%Y").parse;
assert.deepEqual(p("001 01/01/1990"), local(1990, 0, 1));
assert.deepEqual(p("034 02/03/1991"), local(1991, 1, 3));
assert.isNull(p("2012 03/10/2010"));
},
"parses locale date and time": function(format) {
var p = format("%c").parse;
assert.deepEqual(p("Mon Jan 1 00:00:00 1990"), local(1990, 0, 1));
@ -228,6 +278,12 @@ suite.addBatch({
assert.deepEqual(p("12:00:01 pm"), local(1900, 0, 1, 12, 0, 1));
assert.deepEqual(p("11:59:59 PM"), local(1900, 0, 1, 23, 59, 59));
},
"parses literal %": function(format) {
var p = format("%% %m/%d/%Y").parse;
assert.deepEqual(p("% 01/01/1990"), local(1990, 0, 1));
assert.deepEqual(p("% 02/03/1991"), local(1991, 1, 3));
assert.isNull(p("%% 03/10/2010"));
},
"doesn't crash when given weird strings": function(format) {
try {
Object.prototype.foo = 10;