734 lines
21 KiB
JavaScript
734 lines
21 KiB
JavaScript
|
|
|
|
/*
|
|
Gets the browser window size
|
|
|
|
Returns object with height and width properties
|
|
*/
|
|
nv.utils.windowSize = function() {
|
|
// Sane defaults
|
|
var size = {width: 640, height: 480};
|
|
|
|
// Most recent browsers use
|
|
if (window.innerWidth && window.innerHeight) {
|
|
size.width = window.innerWidth;
|
|
size.height = window.innerHeight;
|
|
return (size);
|
|
}
|
|
|
|
// IE can use depending on mode it is in
|
|
if (document.compatMode=='CSS1Compat' &&
|
|
document.documentElement &&
|
|
document.documentElement.offsetWidth ) {
|
|
|
|
size.width = document.documentElement.offsetWidth;
|
|
size.height = document.documentElement.offsetHeight;
|
|
return (size);
|
|
}
|
|
|
|
// Earlier IE uses Doc.body
|
|
if (document.body && document.body.offsetWidth) {
|
|
size.width = document.body.offsetWidth;
|
|
size.height = document.body.offsetHeight;
|
|
return (size);
|
|
}
|
|
|
|
return (size);
|
|
};
|
|
|
|
|
|
/* handle dumb browser quirks... isinstance breaks if you use frames
|
|
typeof returns 'object' for null, NaN is a number, etc.
|
|
*/
|
|
nv.utils.isArray = Array.isArray;
|
|
nv.utils.isObject = function(a) {
|
|
return a !== null && typeof a === 'object';
|
|
};
|
|
nv.utils.isFunction = function(a) {
|
|
return typeof a === 'function';
|
|
};
|
|
nv.utils.isDate = function(a) {
|
|
return toString.call(a) === '[object Date]';
|
|
};
|
|
nv.utils.isNumber = function(a) {
|
|
return !isNaN(a) && typeof a === 'number';
|
|
};
|
|
|
|
|
|
/*
|
|
Binds callback function to run when window is resized
|
|
*/
|
|
nv.utils.windowResize = function(handler) {
|
|
if (window.addEventListener) {
|
|
window.addEventListener('resize', handler);
|
|
} else {
|
|
nv.log("ERROR: Failed to bind to window.resize with: ", handler);
|
|
}
|
|
// return object with clear function to remove the single added callback.
|
|
return {
|
|
callback: handler,
|
|
clear: function() {
|
|
window.removeEventListener('resize', handler);
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
/*
|
|
Backwards compatible way to implement more d3-like coloring of graphs.
|
|
Can take in nothing, an array, or a function/scale
|
|
To use a normal scale, get the range and pass that because we must be able
|
|
to take two arguments and use the index to keep backward compatibility
|
|
*/
|
|
nv.utils.getColor = function(color) {
|
|
//if you pass in nothing, get default colors back
|
|
if (color === undefined) {
|
|
return nv.utils.defaultColor();
|
|
|
|
//if passed an array, turn it into a color scale
|
|
} else if(nv.utils.isArray(color)) {
|
|
var color_scale = d3.scale.ordinal().range(color);
|
|
return function(d, i) {
|
|
var key = i === undefined ? d : i;
|
|
return d.color || color_scale(key);
|
|
};
|
|
|
|
//if passed a function or scale, return it, or whatever it may be
|
|
//external libs, such as angularjs-nvd3-directives use this
|
|
} else {
|
|
//can't really help it if someone passes rubbish as color
|
|
return color;
|
|
}
|
|
};
|
|
|
|
|
|
/*
|
|
Default color chooser uses a color scale of 20 colors from D3
|
|
https://github.com/mbostock/d3/wiki/Ordinal-Scales#categorical-colors
|
|
*/
|
|
nv.utils.defaultColor = function() {
|
|
// get range of the scale so we'll turn it into our own function.
|
|
return nv.utils.getColor(d3.scale.category20().range());
|
|
};
|
|
|
|
|
|
/*
|
|
Returns a color function that takes the result of 'getKey' for each series and
|
|
looks for a corresponding color from the dictionary
|
|
*/
|
|
nv.utils.customTheme = function(dictionary, getKey, defaultColors) {
|
|
// use default series.key if getKey is undefined
|
|
getKey = getKey || function(series) { return series.key };
|
|
defaultColors = defaultColors || d3.scale.category20().range();
|
|
|
|
// start at end of default color list and walk back to index 0
|
|
var defIndex = defaultColors.length;
|
|
|
|
return function(series, index) {
|
|
var key = getKey(series);
|
|
if (nv.utils.isFunction(dictionary[key])) {
|
|
return dictionary[key]();
|
|
} else if (dictionary[key] !== undefined) {
|
|
return dictionary[key];
|
|
} else {
|
|
// no match in dictionary, use a default color
|
|
if (!defIndex) {
|
|
// used all the default colors, start over
|
|
defIndex = defaultColors.length;
|
|
}
|
|
defIndex = defIndex - 1;
|
|
return defaultColors[defIndex];
|
|
}
|
|
};
|
|
};
|
|
|
|
|
|
/*
|
|
From the PJAX example on d3js.org, while this is not really directly needed
|
|
it's a very cool method for doing pjax, I may expand upon it a little bit,
|
|
open to suggestions on anything that may be useful
|
|
*/
|
|
nv.utils.pjax = function(links, content) {
|
|
|
|
var load = function(href) {
|
|
d3.html(href, function(fragment) {
|
|
var target = d3.select(content).node();
|
|
target.parentNode.replaceChild(
|
|
d3.select(fragment).select(content).node(),
|
|
target);
|
|
nv.utils.pjax(links, content);
|
|
});
|
|
};
|
|
|
|
d3.selectAll(links).on("click", function() {
|
|
history.pushState(this.href, this.textContent, this.href);
|
|
load(this.href);
|
|
d3.event.preventDefault();
|
|
});
|
|
|
|
d3.select(window).on("popstate", function() {
|
|
if (d3.event.state) {
|
|
load(d3.event.state);
|
|
}
|
|
});
|
|
};
|
|
|
|
|
|
/*
|
|
For when we want to approximate the width in pixels for an SVG:text element.
|
|
Most common instance is when the element is in a display:none; container.
|
|
Forumla is : text.length * font-size * constant_factor
|
|
*/
|
|
nv.utils.calcApproxTextWidth = function (svgTextElem) {
|
|
if (nv.utils.isFunction(svgTextElem.style) && nv.utils.isFunction(svgTextElem.text)) {
|
|
var fontSize = parseInt(svgTextElem.style("font-size").replace("px",""), 10);
|
|
var textLength = svgTextElem.text().length;
|
|
return nv.utils.NaNtoZero(textLength * fontSize * 0.5);
|
|
}
|
|
return 0;
|
|
};
|
|
|
|
|
|
/*
|
|
Numbers that are undefined, null or NaN, convert them to zeros.
|
|
*/
|
|
nv.utils.NaNtoZero = function(n) {
|
|
if (!nv.utils.isNumber(n)
|
|
|| isNaN(n)
|
|
|| n === null
|
|
|| n === Infinity
|
|
|| n === -Infinity) {
|
|
|
|
return 0;
|
|
}
|
|
return n;
|
|
};
|
|
|
|
/*
|
|
Add a way to watch for d3 transition ends to d3
|
|
*/
|
|
d3.selection.prototype.watchTransition = function(renderWatch){
|
|
var args = [this].concat([].slice.call(arguments, 1));
|
|
return renderWatch.transition.apply(renderWatch, args);
|
|
};
|
|
|
|
|
|
/*
|
|
Helper object to watch when d3 has rendered something
|
|
*/
|
|
nv.utils.renderWatch = function(dispatch, duration) {
|
|
if (!(this instanceof nv.utils.renderWatch)) {
|
|
return new nv.utils.renderWatch(dispatch, duration);
|
|
}
|
|
|
|
var _duration = duration !== undefined ? duration : 250;
|
|
var renderStack = [];
|
|
var self = this;
|
|
|
|
this.models = function(models) {
|
|
models = [].slice.call(arguments, 0);
|
|
models.forEach(function(model){
|
|
model.__rendered = false;
|
|
(function(m){
|
|
m.dispatch.on('renderEnd', function(arg){
|
|
m.__rendered = true;
|
|
self.renderEnd('model');
|
|
});
|
|
})(model);
|
|
|
|
if (renderStack.indexOf(model) < 0) {
|
|
renderStack.push(model);
|
|
}
|
|
});
|
|
return this;
|
|
};
|
|
|
|
this.reset = function(duration) {
|
|
if (duration !== undefined) {
|
|
_duration = duration;
|
|
}
|
|
renderStack = [];
|
|
};
|
|
|
|
this.transition = function(selection, args, duration) {
|
|
args = arguments.length > 1 ? [].slice.call(arguments, 1) : [];
|
|
|
|
if (args.length > 1) {
|
|
duration = args.pop();
|
|
} else {
|
|
duration = _duration !== undefined ? _duration : 250;
|
|
}
|
|
selection.__rendered = false;
|
|
|
|
if (renderStack.indexOf(selection) < 0) {
|
|
renderStack.push(selection);
|
|
}
|
|
|
|
if (duration === 0) {
|
|
selection.__rendered = true;
|
|
selection.delay = function() { return this; };
|
|
selection.duration = function() { return this; };
|
|
return selection;
|
|
} else {
|
|
if (selection.length === 0) {
|
|
selection.__rendered = true;
|
|
} else if (selection.every( function(d){ return !d.length; } )) {
|
|
selection.__rendered = true;
|
|
} else {
|
|
selection.__rendered = false;
|
|
}
|
|
|
|
var n = 0;
|
|
return selection
|
|
.transition()
|
|
.duration(duration)
|
|
.each(function(){ ++n; })
|
|
.each('end', function(d, i) {
|
|
if (--n === 0) {
|
|
selection.__rendered = true;
|
|
self.renderEnd.apply(this, args);
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
this.renderEnd = function() {
|
|
if (renderStack.every( function(d){ return d.__rendered; } )) {
|
|
renderStack.forEach( function(d){ d.__rendered = false; });
|
|
dispatch.renderEnd.apply(this, arguments);
|
|
}
|
|
}
|
|
|
|
};
|
|
|
|
|
|
/*
|
|
Takes multiple objects and combines them into the first one (dst)
|
|
example: nv.utils.deepExtend({a: 1}, {a: 2, b: 3}, {c: 4});
|
|
gives: {a: 2, b: 3, c: 4}
|
|
*/
|
|
nv.utils.deepExtend = function(dst){
|
|
var sources = arguments.length > 1 ? [].slice.call(arguments, 1) : [];
|
|
sources.forEach(function(source) {
|
|
for (var key in source) {
|
|
var isArray = nv.utils.isArray(dst[key]);
|
|
var isObject = nv.utils.isObject(dst[key]);
|
|
var srcObj = nv.utils.isObject(source[key]);
|
|
|
|
if (isObject && !isArray && srcObj) {
|
|
nv.utils.deepExtend(dst[key], source[key]);
|
|
} else {
|
|
dst[key] = source[key];
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
|
|
/*
|
|
state utility object, used to track d3 states in the models
|
|
*/
|
|
nv.utils.state = function(){
|
|
if (!(this instanceof nv.utils.state)) {
|
|
return new nv.utils.state();
|
|
}
|
|
var state = {};
|
|
var _self = this;
|
|
var _setState = function(){};
|
|
var _getState = function(){ return {}; };
|
|
var init = null;
|
|
var changed = null;
|
|
|
|
this.dispatch = d3.dispatch('change', 'set');
|
|
|
|
this.dispatch.on('set', function(state){
|
|
_setState(state, true);
|
|
});
|
|
|
|
this.getter = function(fn){
|
|
_getState = fn;
|
|
return this;
|
|
};
|
|
|
|
this.setter = function(fn, callback) {
|
|
if (!callback) {
|
|
callback = function(){};
|
|
}
|
|
_setState = function(state, update){
|
|
fn(state);
|
|
if (update) {
|
|
callback();
|
|
}
|
|
};
|
|
return this;
|
|
};
|
|
|
|
this.init = function(state){
|
|
init = init || {};
|
|
nv.utils.deepExtend(init, state);
|
|
};
|
|
|
|
var _set = function(){
|
|
var settings = _getState();
|
|
|
|
if (JSON.stringify(settings) === JSON.stringify(state)) {
|
|
return false;
|
|
}
|
|
|
|
for (var key in settings) {
|
|
if (state[key] === undefined) {
|
|
state[key] = {};
|
|
}
|
|
state[key] = settings[key];
|
|
changed = true;
|
|
}
|
|
return true;
|
|
};
|
|
|
|
this.update = function(){
|
|
if (init) {
|
|
_setState(init, false);
|
|
init = null;
|
|
}
|
|
if (_set.call(this)) {
|
|
this.dispatch.change(state);
|
|
}
|
|
};
|
|
|
|
};
|
|
|
|
|
|
/*
|
|
Snippet of code you can insert into each nv.models.* to give you the ability to
|
|
do things like:
|
|
chart.options({
|
|
showXAxis: true,
|
|
tooltips: true
|
|
});
|
|
|
|
To enable in the chart:
|
|
chart.options = nv.utils.optionsFunc.bind(chart);
|
|
*/
|
|
nv.utils.optionsFunc = function(args) {
|
|
if (args) {
|
|
d3.map(args).forEach((function(key,value) {
|
|
if (nv.utils.isFunction(this[key])) {
|
|
this[key](value);
|
|
}
|
|
}).bind(this));
|
|
}
|
|
return this;
|
|
};
|
|
|
|
|
|
/*
|
|
numTicks: requested number of ticks
|
|
data: the chart data
|
|
|
|
returns the number of ticks to actually use on X axis, based on chart data
|
|
to avoid duplicate ticks with the same value
|
|
*/
|
|
nv.utils.calcTicksX = function(numTicks, data) {
|
|
// find max number of values from all data streams
|
|
var numValues = 1;
|
|
var i = 0;
|
|
for (i; i < data.length; i += 1) {
|
|
var stream_len = data[i] && data[i].values ? data[i].values.length : 0;
|
|
numValues = stream_len > numValues ? stream_len : numValues;
|
|
}
|
|
nv.log("Requested number of ticks: ", numTicks);
|
|
nv.log("Calculated max values to be: ", numValues);
|
|
// make sure we don't have more ticks than values to avoid duplicates
|
|
numTicks = numTicks > numValues ? numTicks = numValues - 1 : numTicks;
|
|
// make sure we have at least one tick
|
|
numTicks = numTicks < 1 ? 1 : numTicks;
|
|
// make sure it's an integer
|
|
numTicks = Math.floor(numTicks);
|
|
nv.log("Calculating tick count as: ", numTicks);
|
|
return numTicks;
|
|
};
|
|
|
|
|
|
/*
|
|
returns number of ticks to actually use on Y axis, based on chart data
|
|
*/
|
|
nv.utils.calcTicksY = function(numTicks, data) {
|
|
// currently uses the same logic but we can adjust here if needed later
|
|
return nv.utils.calcTicksX(numTicks, data);
|
|
};
|
|
|
|
|
|
/*
|
|
Add a particular option from an options object onto chart
|
|
Options exposed on a chart are a getter/setter function that returns chart
|
|
on set to mimic typical d3 option chaining, e.g. svg.option1('a').option2('b');
|
|
|
|
option objects should be generated via Object.create() to provide
|
|
the option of manipulating data via get/set functions.
|
|
*/
|
|
nv.utils.initOption = function(chart, name) {
|
|
// if it's a call option, just call it directly, otherwise do get/set
|
|
if (chart._calls && chart._calls[name]) {
|
|
chart[name] = chart._calls[name];
|
|
} else {
|
|
chart[name] = function (_) {
|
|
if (!arguments.length) return chart._options[name];
|
|
chart._overrides[name] = true;
|
|
chart._options[name] = _;
|
|
return chart;
|
|
};
|
|
// calling the option as _option will ignore if set by option already
|
|
// so nvd3 can set options internally but the stop if set manually
|
|
chart['_' + name] = function(_) {
|
|
if (!arguments.length) return chart._options[name];
|
|
if (!chart._overrides[name]) {
|
|
chart._options[name] = _;
|
|
}
|
|
return chart;
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
/*
|
|
Add all options in an options object to the chart
|
|
*/
|
|
nv.utils.initOptions = function(chart) {
|
|
chart._overrides = chart._overrides || {};
|
|
var ops = Object.getOwnPropertyNames(chart._options || {});
|
|
var calls = Object.getOwnPropertyNames(chart._calls || {});
|
|
ops = ops.concat(calls);
|
|
for (var i in ops) {
|
|
nv.utils.initOption(chart, ops[i]);
|
|
}
|
|
};
|
|
|
|
|
|
/*
|
|
Inherit options from a D3 object
|
|
d3.rebind makes calling the function on target actually call it on source
|
|
Also use _d3options so we can track what we inherit for documentation and chained inheritance
|
|
*/
|
|
nv.utils.inheritOptionsD3 = function(target, d3_source, oplist) {
|
|
target._d3options = oplist.concat(target._d3options || []);
|
|
// Find unique d3 options (string) and update d3options
|
|
target._d3options = (target._d3options || []).filter(function(item, i, ar){ return ar.indexOf(item) === i; });
|
|
oplist.unshift(d3_source);
|
|
oplist.unshift(target);
|
|
d3.rebind.apply(this, oplist);
|
|
};
|
|
|
|
|
|
/*
|
|
Remove duplicates from an array
|
|
*/
|
|
nv.utils.arrayUnique = function(a) {
|
|
return a.sort().filter(function(item, pos) {
|
|
return !pos || item != a[pos - 1];
|
|
});
|
|
};
|
|
|
|
|
|
/*
|
|
Keeps a list of custom symbols to draw from in addition to d3.svg.symbol
|
|
Necessary since d3 doesn't let you extend its list -_-
|
|
Add new symbols by doing nv.utils.symbols.set('name', function(size){...});
|
|
*/
|
|
nv.utils.symbolMap = d3.map();
|
|
|
|
|
|
/*
|
|
Replaces d3.svg.symbol so that we can look both there and our own map
|
|
*/
|
|
nv.utils.symbol = function() {
|
|
var type,
|
|
size = 64;
|
|
function symbol(d,i) {
|
|
var t = type.call(this,d,i);
|
|
var s = size.call(this,d,i);
|
|
if (d3.svg.symbolTypes.indexOf(t) !== -1) {
|
|
return d3.svg.symbol().type(t).size(s)();
|
|
} else {
|
|
return nv.utils.symbolMap.get(t)(s);
|
|
}
|
|
}
|
|
symbol.type = function(_) {
|
|
if (!arguments.length) return type;
|
|
type = d3.functor(_);
|
|
return symbol;
|
|
};
|
|
symbol.size = function(_) {
|
|
if (!arguments.length) return size;
|
|
size = d3.functor(_);
|
|
return symbol;
|
|
};
|
|
return symbol;
|
|
};
|
|
|
|
|
|
/*
|
|
Inherit option getter/setter functions from source to target
|
|
d3.rebind makes calling the function on target actually call it on source
|
|
Also track via _inherited and _d3options so we can track what we inherit
|
|
for documentation generation purposes and chained inheritance
|
|
*/
|
|
nv.utils.inheritOptions = function(target, source) {
|
|
// inherit all the things
|
|
var ops = Object.getOwnPropertyNames(source._options || {});
|
|
var calls = Object.getOwnPropertyNames(source._calls || {});
|
|
var inherited = source._inherited || [];
|
|
var d3ops = source._d3options || [];
|
|
var args = ops.concat(calls).concat(inherited).concat(d3ops);
|
|
args.unshift(source);
|
|
args.unshift(target);
|
|
d3.rebind.apply(this, args);
|
|
// pass along the lists to keep track of them, don't allow duplicates
|
|
target._inherited = nv.utils.arrayUnique(ops.concat(calls).concat(inherited).concat(ops).concat(target._inherited || []));
|
|
target._d3options = nv.utils.arrayUnique(d3ops.concat(target._d3options || []));
|
|
};
|
|
|
|
|
|
/*
|
|
Runs common initialize code on the svg before the chart builds
|
|
*/
|
|
nv.utils.initSVG = function(svg) {
|
|
svg.classed({'nvd3-svg':true});
|
|
};
|
|
|
|
|
|
/*
|
|
Sanitize and provide default for the container height.
|
|
*/
|
|
nv.utils.sanitizeHeight = function(height, container) {
|
|
return (height || parseInt(container.style('height'), 10) || 400);
|
|
};
|
|
|
|
|
|
/*
|
|
Sanitize and provide default for the container width.
|
|
*/
|
|
nv.utils.sanitizeWidth = function(width, container) {
|
|
return (width || parseInt(container.style('width'), 10) || 960);
|
|
};
|
|
|
|
|
|
/*
|
|
Calculate the available height for a chart.
|
|
*/
|
|
nv.utils.availableHeight = function(height, container, margin) {
|
|
return Math.max(0,nv.utils.sanitizeHeight(height, container) - margin.top - margin.bottom);
|
|
};
|
|
|
|
/*
|
|
Calculate the available width for a chart.
|
|
*/
|
|
nv.utils.availableWidth = function(width, container, margin) {
|
|
return Math.max(0,nv.utils.sanitizeWidth(width, container) - margin.left - margin.right);
|
|
};
|
|
|
|
/*
|
|
Clear any rendered chart components and display a chart's 'noData' message
|
|
*/
|
|
nv.utils.noData = function(chart, container) {
|
|
var opt = chart.options(),
|
|
margin = opt.margin(),
|
|
noData = opt.noData(),
|
|
data = (noData == null) ? ["No Data Available."] : [noData],
|
|
height = nv.utils.availableHeight(null, container, margin),
|
|
width = nv.utils.availableWidth(null, container, margin),
|
|
x = margin.left + width/2,
|
|
y = margin.top + height/2;
|
|
|
|
//Remove any previously created chart components
|
|
container.selectAll('g').remove();
|
|
|
|
var noDataText = container.selectAll('.nv-noData').data(data);
|
|
|
|
noDataText.enter().append('text')
|
|
.attr('class', 'nvd3 nv-noData')
|
|
.attr('dy', '-.7em')
|
|
.style('text-anchor', 'middle');
|
|
|
|
noDataText
|
|
.attr('x', x)
|
|
.attr('y', y)
|
|
.text(function(t){ return t; });
|
|
};
|
|
|
|
/*
|
|
Wrap long labels.
|
|
*/
|
|
nv.utils.wrapTicks = function (text, width) {
|
|
text.each(function() {
|
|
var text = d3.select(this),
|
|
words = text.text().split(/\s+/).reverse(),
|
|
word,
|
|
line = [],
|
|
lineNumber = 0,
|
|
lineHeight = 1.1,
|
|
y = text.attr("y"),
|
|
dy = parseFloat(text.attr("dy")),
|
|
tspan = text.text(null).append("tspan").attr("x", 0).attr("y", y).attr("dy", dy + "em");
|
|
while (word = words.pop()) {
|
|
line.push(word);
|
|
tspan.text(line.join(" "));
|
|
if (tspan.node().getComputedTextLength() > width) {
|
|
line.pop();
|
|
tspan.text(line.join(" "));
|
|
line = [word];
|
|
tspan = text.append("tspan").attr("x", 0).attr("y", y).attr("dy", ++lineNumber * lineHeight + dy + "em").text(word);
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
/*
|
|
Check equality of 2 array
|
|
*/
|
|
nv.utils.arrayEquals = function (array1, array2) {
|
|
if (array1 === array2)
|
|
return true;
|
|
|
|
if (!array1 || !array2)
|
|
return false;
|
|
|
|
// compare lengths - can save a lot of time
|
|
if (array1.length != array2.length)
|
|
return false;
|
|
|
|
for (var i = 0,
|
|
l = array1.length; i < l; i++) {
|
|
// Check if we have nested arrays
|
|
if (array1[i] instanceof Array && array2[i] instanceof Array) {
|
|
// recurse into the nested arrays
|
|
if (!nv.arrayEquals(array1[i], array2[i]))
|
|
return false;
|
|
} else if (array1[i] != array2[i]) {
|
|
// Warning - two different object instances will never be equal: {x:20} != {x:20}
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
};
|
|
|
|
/*
|
|
Check if a point within an arc
|
|
*/
|
|
nv.utils.pointIsInArc = function(pt, ptData, d3Arc) {
|
|
// Center of the arc is assumed to be 0,0
|
|
// (pt.x, pt.y) are assumed to be relative to the center
|
|
var r1 = d3Arc.innerRadius()(ptData), // Note: Using the innerRadius
|
|
r2 = d3Arc.outerRadius()(ptData),
|
|
theta1 = d3Arc.startAngle()(ptData),
|
|
theta2 = d3Arc.endAngle()(ptData);
|
|
|
|
var dist = pt.x * pt.x + pt.y * pt.y,
|
|
angle = Math.atan2(pt.x, -pt.y); // Note: different coordinate system.
|
|
|
|
angle = (angle < 0) ? (angle + Math.PI * 2) : angle;
|
|
|
|
return (r1 * r1 <= dist) && (dist <= r2 * r2) &&
|
|
(theta1 <= angle) && (angle <= theta2);
|
|
};
|
|
|