-
-
Save mih4ajlo/a4e9ec8790ea92b4cadee337a39082f2 to your computer and use it in GitHub Desktop.
Test force layout tree
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Editor backup files | |
| *.bak | |
| *~ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| (function(){if (!Date.now) Date.now = function() { | |
| return +new Date; | |
| }; | |
| try { | |
| document.createElement("div").style.setProperty("opacity", 0, ""); | |
| } catch (error) { | |
| var d3_style_prototype = CSSStyleDeclaration.prototype, | |
| d3_style_setProperty = d3_style_prototype.setProperty; | |
| d3_style_prototype.setProperty = function(name, value, priority) { | |
| d3_style_setProperty.call(this, name, value + "", priority); | |
| }; | |
| } | |
| d3 = {version: "2.10.1"}; // semver | |
| function d3_class(ctor, properties) { | |
| try { | |
| for (var key in properties) { | |
| Object.defineProperty(ctor.prototype, key, { | |
| value: properties[key], | |
| enumerable: false | |
| }); | |
| } | |
| } catch (e) { | |
| ctor.prototype = properties; | |
| } | |
| } | |
| var d3_array = d3_arraySlice; // conversion for NodeLists | |
| function d3_arrayCopy(pseudoarray) { | |
| var i = -1, n = pseudoarray.length, array = []; | |
| while (++i < n) array.push(pseudoarray[i]); | |
| return array; | |
| } | |
| function d3_arraySlice(pseudoarray) { | |
| return Array.prototype.slice.call(pseudoarray); | |
| } | |
| try { | |
| d3_array(document.documentElement.childNodes)[0].nodeType; | |
| } catch(e) { | |
| d3_array = d3_arrayCopy; | |
| } | |
| var d3_arraySubclass = [].__proto__? | |
| // Until ECMAScript supports array subclassing, prototype injection works well. | |
| function(array, prototype) { | |
| array.__proto__ = prototype; | |
| }: | |
| // And if your browser doesn't support __proto__, we'll use direct extension. | |
| function(array, prototype) { | |
| for (var property in prototype) array[property] = prototype[property]; | |
| }; | |
| d3.map = function(object) { | |
| var map = new d3_Map; | |
| for (var key in object) map.set(key, object[key]); | |
| return map; | |
| }; | |
| function d3_Map() {} | |
| d3_class(d3_Map, { | |
| has: function(key) { | |
| return d3_map_prefix + key in this; | |
| }, | |
| get: function(key) { | |
| return this[d3_map_prefix + key]; | |
| }, | |
| set: function(key, value) { | |
| return this[d3_map_prefix + key] = value; | |
| }, | |
| remove: function(key) { | |
| key = d3_map_prefix + key; | |
| return key in this && delete this[key]; | |
| }, | |
| keys: function() { | |
| var keys = []; | |
| this.forEach(function(key) { keys.push(key); }); | |
| return keys; | |
| }, | |
| values: function() { | |
| var values = []; | |
| this.forEach(function(key, value) { values.push(value); }); | |
| return values; | |
| }, | |
| entries: function() { | |
| var entries = []; | |
| this.forEach(function(key, value) { entries.push({key: key, value: value}); }); | |
| return entries; | |
| }, | |
| forEach: function(f) { | |
| for (var key in this) { | |
| if (key.charCodeAt(0) === d3_map_prefixCode) { | |
| f.call(this, key.substring(1), this[key]); | |
| } | |
| } | |
| } | |
| }); | |
| var d3_map_prefix = "\0", // prevent collision with built-ins | |
| d3_map_prefixCode = d3_map_prefix.charCodeAt(0); | |
| function d3_identity(d) { | |
| return d; | |
| } | |
| function d3_this() { | |
| return this; | |
| } | |
| function d3_true() { | |
| return true; | |
| } | |
| function d3_functor(v) { | |
| return typeof v === "function" ? v : function() { return v; }; | |
| } | |
| d3.functor = d3_functor; | |
| // Copies a variable number of methods from source to target. | |
| d3.rebind = function(target, source) { | |
| var i = 1, n = arguments.length, method; | |
| while (++i < n) target[method = arguments[i]] = d3_rebind(target, source, source[method]); | |
| return target; | |
| }; | |
| // Method is assumed to be a standard D3 getter-setter: | |
| // If passed with no arguments, gets the value. | |
| // If passed with arguments, sets the value and returns the target. | |
| function d3_rebind(target, source, method) { | |
| return function() { | |
| var value = method.apply(source, arguments); | |
| return arguments.length ? target : value; | |
| }; | |
| } | |
| d3.ascending = function(a, b) { | |
| return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN; | |
| }; | |
| d3.descending = function(a, b) { | |
| return b < a ? -1 : b > a ? 1 : b >= a ? 0 : NaN; | |
| }; | |
| d3.mean = function(array, f) { | |
| var n = array.length, | |
| a, | |
| m = 0, | |
| i = -1, | |
| j = 0; | |
| if (arguments.length === 1) { | |
| while (++i < n) if (d3_number(a = array[i])) m += (a - m) / ++j; | |
| } else { | |
| while (++i < n) if (d3_number(a = f.call(array, array[i], i))) m += (a - m) / ++j; | |
| } | |
| return j ? m : undefined; | |
| }; | |
| d3.median = function(array, f) { | |
| if (arguments.length > 1) array = array.map(f); | |
| array = array.filter(d3_number); | |
| return array.length ? d3.quantile(array.sort(d3.ascending), .5) : undefined; | |
| }; | |
| d3.min = function(array, f) { | |
| var i = -1, | |
| n = array.length, | |
| a, | |
| b; | |
| if (arguments.length === 1) { | |
| while (++i < n && ((a = array[i]) == null || a != a)) a = undefined; | |
| while (++i < n) if ((b = array[i]) != null && a > b) a = b; | |
| } else { | |
| while (++i < n && ((a = f.call(array, array[i], i)) == null || a != a)) a = undefined; | |
| while (++i < n) if ((b = f.call(array, array[i], i)) != null && a > b) a = b; | |
| } | |
| return a; | |
| }; | |
| d3.max = function(array, f) { | |
| var i = -1, | |
| n = array.length, | |
| a, | |
| b; | |
| if (arguments.length === 1) { | |
| while (++i < n && ((a = array[i]) == null || a != a)) a = undefined; | |
| while (++i < n) if ((b = array[i]) != null && b > a) a = b; | |
| } else { | |
| while (++i < n && ((a = f.call(array, array[i], i)) == null || a != a)) a = undefined; | |
| while (++i < n) if ((b = f.call(array, array[i], i)) != null && b > a) a = b; | |
| } | |
| return a; | |
| }; | |
| d3.extent = function(array, f) { | |
| var i = -1, | |
| n = array.length, | |
| a, | |
| b, | |
| c; | |
| if (arguments.length === 1) { | |
| while (++i < n && ((a = c = array[i]) == null || a != a)) a = c = undefined; | |
| while (++i < n) if ((b = array[i]) != null) { | |
| if (a > b) a = b; | |
| if (c < b) c = b; | |
| } | |
| } else { | |
| while (++i < n && ((a = c = f.call(array, array[i], i)) == null || a != a)) a = undefined; | |
| while (++i < n) if ((b = f.call(array, array[i], i)) != null) { | |
| if (a > b) a = b; | |
| if (c < b) c = b; | |
| } | |
| } | |
| return [a, c]; | |
| }; | |
| d3.random = { | |
| normal: function(µ, σ) { | |
| var n = arguments.length; | |
| if (n < 2) σ = 1; | |
| if (n < 1) µ = 0; | |
| return function() { | |
| var x, y, r; | |
| do { | |
| x = Math.random() * 2 - 1; | |
| y = Math.random() * 2 - 1; | |
| r = x * x + y * y; | |
| } while (!r || r > 1); | |
| return µ + σ * x * Math.sqrt(-2 * Math.log(r) / r); | |
| }; | |
| }, | |
| logNormal: function(µ, σ) { | |
| var n = arguments.length; | |
| if (n < 2) σ = 1; | |
| if (n < 1) µ = 0; | |
| var random = d3.random.normal(); | |
| return function() { | |
| return Math.exp(µ + σ * random()); | |
| }; | |
| }, | |
| irwinHall: function(m) { | |
| return function() { | |
| for (var s = 0, j = 0; j < m; j++) s += Math.random(); | |
| return s / m; | |
| }; | |
| } | |
| }; | |
| function d3_number(x) { | |
| return x != null && !isNaN(x); | |
| } | |
| d3.sum = function(array, f) { | |
| var s = 0, | |
| n = array.length, | |
| a, | |
| i = -1; | |
| if (arguments.length === 1) { | |
| while (++i < n) if (!isNaN(a = +array[i])) s += a; | |
| } else { | |
| while (++i < n) if (!isNaN(a = +f.call(array, array[i], i))) s += a; | |
| } | |
| return s; | |
| }; | |
| // R-7 per <http://en.wikipedia.org/wiki/Quantile> | |
| d3.quantile = function(values, p) { | |
| var H = (values.length - 1) * p + 1, | |
| h = Math.floor(H), | |
| v = values[h - 1], | |
| e = H - h; | |
| return e ? v + e * (values[h] - v) : v; | |
| }; | |
| d3.transpose = function(matrix) { | |
| return d3.zip.apply(d3, matrix); | |
| }; | |
| d3.zip = function() { | |
| if (!(n = arguments.length)) return []; | |
| for (var i = -1, m = d3.min(arguments, d3_zipLength), zips = new Array(m); ++i < m;) { | |
| for (var j = -1, n, zip = zips[i] = new Array(n); ++j < n;) { | |
| zip[j] = arguments[j][i]; | |
| } | |
| } | |
| return zips; | |
| }; | |
| function d3_zipLength(d) { | |
| return d.length; | |
| } | |
| d3.bisector = function(f) { | |
| return { | |
| left: function(a, x, lo, hi) { | |
| if (arguments.length < 3) lo = 0; | |
| if (arguments.length < 4) hi = a.length; | |
| while (lo < hi) { | |
| var mid = lo + hi >>> 1; | |
| if (f.call(a, a[mid], mid) < x) lo = mid + 1; | |
| else hi = mid; | |
| } | |
| return lo; | |
| }, | |
| right: function(a, x, lo, hi) { | |
| if (arguments.length < 3) lo = 0; | |
| if (arguments.length < 4) hi = a.length; | |
| while (lo < hi) { | |
| var mid = lo + hi >>> 1; | |
| if (x < f.call(a, a[mid], mid)) hi = mid; | |
| else lo = mid + 1; | |
| } | |
| return lo; | |
| } | |
| }; | |
| }; | |
| var d3_bisector = d3.bisector(function(d) { return d; }); | |
| d3.bisectLeft = d3_bisector.left; | |
| d3.bisect = d3.bisectRight = d3_bisector.right; | |
| d3.first = function(array, f) { | |
| var i = 0, | |
| n = array.length, | |
| a = array[0], | |
| b; | |
| if (arguments.length === 1) f = d3.ascending; | |
| while (++i < n) { | |
| if (f.call(array, a, b = array[i]) > 0) { | |
| a = b; | |
| } | |
| } | |
| return a; | |
| }; | |
| d3.last = function(array, f) { | |
| var i = 0, | |
| n = array.length, | |
| a = array[0], | |
| b; | |
| if (arguments.length === 1) f = d3.ascending; | |
| while (++i < n) { | |
| if (f.call(array, a, b = array[i]) <= 0) { | |
| a = b; | |
| } | |
| } | |
| return a; | |
| }; | |
| d3.nest = function() { | |
| var nest = {}, | |
| keys = [], | |
| sortKeys = [], | |
| sortValues, | |
| rollup; | |
| function map(array, depth) { | |
| if (depth >= keys.length) return rollup | |
| ? rollup.call(nest, array) : (sortValues | |
| ? array.sort(sortValues) | |
| : array); | |
| var i = -1, | |
| n = array.length, | |
| key = keys[depth++], | |
| keyValue, | |
| object, | |
| valuesByKey = new d3_Map, | |
| values, | |
| o = {}; | |
| while (++i < n) { | |
| if (values = valuesByKey.get(keyValue = key(object = array[i]))) { | |
| values.push(object); | |
| } else { | |
| valuesByKey.set(keyValue, [object]); | |
| } | |
| } | |
| valuesByKey.forEach(function(keyValue, values) { | |
| o[keyValue] = map(values, depth); | |
| }); | |
| return o; | |
| } | |
| function entries(map, depth) { | |
| if (depth >= keys.length) return map; | |
| var a = [], | |
| sortKey = sortKeys[depth++], | |
| key; | |
| for (key in map) { | |
| a.push({key: key, values: entries(map[key], depth)}); | |
| } | |
| if (sortKey) a.sort(function(a, b) { | |
| return sortKey(a.key, b.key); | |
| }); | |
| return a; | |
| } | |
| nest.map = function(array) { | |
| return map(array, 0); | |
| }; | |
| nest.entries = function(array) { | |
| return entries(map(array, 0), 0); | |
| }; | |
| nest.key = function(d) { | |
| keys.push(d); | |
| return nest; | |
| }; | |
| // Specifies the order for the most-recently specified key. | |
| // Note: only applies to entries. Map keys are unordered! | |
| nest.sortKeys = function(order) { | |
| sortKeys[keys.length - 1] = order; | |
| return nest; | |
| }; | |
| // Specifies the order for leaf values. | |
| // Applies to both maps and entries array. | |
| nest.sortValues = function(order) { | |
| sortValues = order; | |
| return nest; | |
| }; | |
| nest.rollup = function(f) { | |
| rollup = f; | |
| return nest; | |
| }; | |
| return nest; | |
| }; | |
| d3.keys = function(map) { | |
| var keys = []; | |
| for (var key in map) keys.push(key); | |
| return keys; | |
| }; | |
| d3.values = function(map) { | |
| var values = []; | |
| for (var key in map) values.push(map[key]); | |
| return values; | |
| }; | |
| d3.entries = function(map) { | |
| var entries = []; | |
| for (var key in map) entries.push({key: key, value: map[key]}); | |
| return entries; | |
| }; | |
| d3.permute = function(array, indexes) { | |
| var permutes = [], | |
| i = -1, | |
| n = indexes.length; | |
| while (++i < n) permutes[i] = array[indexes[i]]; | |
| return permutes; | |
| }; | |
| d3.merge = function(arrays) { | |
| return Array.prototype.concat.apply([], arrays); | |
| }; | |
| d3.split = function(array, f) { | |
| var arrays = [], | |
| values = [], | |
| value, | |
| i = -1, | |
| n = array.length; | |
| if (arguments.length < 2) f = d3_splitter; | |
| while (++i < n) { | |
| if (f.call(values, value = array[i], i)) { | |
| values = []; | |
| } else { | |
| if (!values.length) arrays.push(values); | |
| values.push(value); | |
| } | |
| } | |
| return arrays; | |
| }; | |
| function d3_splitter(d) { | |
| return d == null; | |
| } | |
| function d3_collapse(s) { | |
| return s.trim().replace(/\s+/g, " "); | |
| } | |
| d3.range = function(start, stop, step) { | |
| if (arguments.length < 3) { | |
| step = 1; | |
| if (arguments.length < 2) { | |
| stop = start; | |
| start = 0; | |
| } | |
| } | |
| if ((stop - start) / step === Infinity) throw new Error("infinite range"); | |
| var range = [], | |
| k = d3_range_integerScale(Math.abs(step)), | |
| i = -1, | |
| j; | |
| start *= k, stop *= k, step *= k; | |
| if (step < 0) while ((j = start + step * ++i) > stop) range.push(j / k); | |
| else while ((j = start + step * ++i) < stop) range.push(j / k); | |
| return range; | |
| }; | |
| function d3_range_integerScale(x) { | |
| var k = 1; | |
| while (x * k % 1) k *= 10; | |
| return k; | |
| } | |
| d3.requote = function(s) { | |
| return s.replace(d3_requote_re, "\\$&"); | |
| }; | |
| var d3_requote_re = /[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g; | |
| d3.round = function(x, n) { | |
| return n | |
| ? Math.round(x * (n = Math.pow(10, n))) / n | |
| : Math.round(x); | |
| }; | |
| d3.xhr = function(url, mime, callback) { | |
| var req = new XMLHttpRequest; | |
| if (arguments.length < 3) callback = mime, mime = null; | |
| else if (mime && req.overrideMimeType) req.overrideMimeType(mime); | |
| req.open("GET", url, true); | |
| if (mime) req.setRequestHeader("Accept", mime + ",*/*"); | |
| req.onreadystatechange = function() { | |
| if (req.readyState === 4) { | |
| var s = req.status; | |
| callback(!s && req.response || s >= 200 && s < 300 || s === 304 ? req : null); | |
| } | |
| }; | |
| req.send(null); | |
| }; | |
| d3.text = function(url, mime, callback) { | |
| function ready(req) { | |
| callback(req && req.responseText); | |
| } | |
| if (arguments.length < 3) { | |
| callback = mime; | |
| mime = null; | |
| } | |
| d3.xhr(url, mime, ready); | |
| }; | |
| d3.json = function(url, callback) { | |
| d3.text(url, "application/json", function(text) { | |
| callback(text ? JSON.parse(text) : null); | |
| }); | |
| }; | |
| d3.html = function(url, callback) { | |
| d3.text(url, "text/html", function(text) { | |
| if (text != null) { // Treat empty string as valid HTML. | |
| var range = document.createRange(); | |
| range.selectNode(document.body); | |
| text = range.createContextualFragment(text); | |
| } | |
| callback(text); | |
| }); | |
| }; | |
| d3.xml = function(url, mime, callback) { | |
| function ready(req) { | |
| callback(req && req.responseXML); | |
| } | |
| if (arguments.length < 3) { | |
| callback = mime; | |
| mime = null; | |
| } | |
| d3.xhr(url, mime, ready); | |
| }; | |
| var d3_nsPrefix = { | |
| svg: "http://www.w3.org/2000/svg", | |
| xhtml: "http://www.w3.org/1999/xhtml", | |
| xlink: "http://www.w3.org/1999/xlink", | |
| xml: "http://www.w3.org/XML/1998/namespace", | |
| xmlns: "http://www.w3.org/2000/xmlns/" | |
| }; | |
| d3.ns = { | |
| prefix: d3_nsPrefix, | |
| qualify: function(name) { | |
| var i = name.indexOf(":"), | |
| prefix = name; | |
| if (i >= 0) { | |
| prefix = name.substring(0, i); | |
| name = name.substring(i + 1); | |
| } | |
| return d3_nsPrefix.hasOwnProperty(prefix) | |
| ? {space: d3_nsPrefix[prefix], local: name} | |
| : name; | |
| } | |
| }; | |
| d3.dispatch = function() { | |
| var dispatch = new d3_dispatch, | |
| i = -1, | |
| n = arguments.length; | |
| while (++i < n) dispatch[arguments[i]] = d3_dispatch_event(dispatch); | |
| return dispatch; | |
| }; | |
| function d3_dispatch() {} | |
| d3_dispatch.prototype.on = function(type, listener) { | |
| var i = type.indexOf("."), | |
| name = ""; | |
| // Extract optional namespace, e.g., "click.foo" | |
| if (i > 0) { | |
| name = type.substring(i + 1); | |
| type = type.substring(0, i); | |
| } | |
| return arguments.length < 2 | |
| ? this[type].on(name) | |
| : this[type].on(name, listener); | |
| }; | |
| function d3_dispatch_event(dispatch) { | |
| var listeners = [], | |
| listenerByName = new d3_Map; | |
| function event() { | |
| var z = listeners, // defensive reference | |
| i = -1, | |
| n = z.length, | |
| l; | |
| while (++i < n) if (l = z[i].on) l.apply(this, arguments); | |
| return dispatch; | |
| } | |
| event.on = function(name, listener) { | |
| var l = listenerByName.get(name), | |
| i; | |
| // return the current listener, if any | |
| if (arguments.length < 2) return l && l.on; | |
| // remove the old listener, if any (with copy-on-write) | |
| if (l) { | |
| l.on = null; | |
| listeners = listeners.slice(0, i = listeners.indexOf(l)).concat(listeners.slice(i + 1)); | |
| listenerByName.remove(name); | |
| } | |
| // add the new listener, if any | |
| if (listener) listeners.push(listenerByName.set(name, {on: listener})); | |
| return dispatch; | |
| }; | |
| return event; | |
| } | |
| // TODO align | |
| d3.format = function(specifier) { | |
| var match = d3_format_re.exec(specifier), | |
| fill = match[1] || " ", | |
| sign = match[3] || "", | |
| zfill = match[5], | |
| width = +match[6], | |
| comma = match[7], | |
| precision = match[8], | |
| type = match[9], | |
| scale = 1, | |
| suffix = "", | |
| integer = false; | |
| if (precision) precision = +precision.substring(1); | |
| if (zfill) { | |
| fill = "0"; // TODO align = "="; | |
| if (comma) width -= Math.floor((width - 1) / 4); | |
| } | |
| switch (type) { | |
| case "n": comma = true; type = "g"; break; | |
| case "%": scale = 100; suffix = "%"; type = "f"; break; | |
| case "p": scale = 100; suffix = "%"; type = "r"; break; | |
| case "d": integer = true; precision = 0; break; | |
| case "s": scale = -1; type = "r"; break; | |
| } | |
| // If no precision is specified for r, fallback to general notation. | |
| if (type == "r" && !precision) type = "g"; | |
| type = d3_format_types.get(type) || d3_format_typeDefault; | |
| return function(value) { | |
| // Return the empty string for floats formatted as ints. | |
| if (integer && (value % 1)) return ""; | |
| // Convert negative to positive, and record the sign prefix. | |
| var negative = value < 0 && (value = -value) ? "-" : sign; | |
| // Apply the scale, computing it from the value's exponent for si format. | |
| if (scale < 0) { | |
| var prefix = d3.formatPrefix(value, precision); | |
| value = prefix.scale(value); | |
| suffix = prefix.symbol; | |
| } else { | |
| value *= scale; | |
| } | |
| // Convert to the desired precision. | |
| value = type(value, precision); | |
| // If the fill character is 0, the sign and group is applied after the fill. | |
| if (zfill) { | |
| var length = value.length + negative.length; | |
| if (length < width) value = new Array(width - length + 1).join(fill) + value; | |
| if (comma) value = d3_format_group(value); | |
| value = negative + value; | |
| } | |
| // Otherwise (e.g., space-filling), the sign and group is applied before. | |
| else { | |
| if (comma) value = d3_format_group(value); | |
| value = negative + value; | |
| var length = value.length; | |
| if (length < width) value = new Array(width - length + 1).join(fill) + value; | |
| } | |
| return value + suffix; | |
| }; | |
| }; | |
| // [[fill]align][sign][#][0][width][,][.precision][type] | |
| var d3_format_re = /(?:([^{])?([<>=^]))?([+\- ])?(#)?(0)?([0-9]+)?(,)?(\.[0-9]+)?([a-zA-Z%])?/; | |
| var d3_format_types = d3.map({ | |
| g: function(x, p) { return x.toPrecision(p); }, | |
| e: function(x, p) { return x.toExponential(p); }, | |
| E: function(x, p) { | |
| var rv; | |
| var p1 = d3_format_precision(x, 1); | |
| if (p1 >= -5 && p1 <= 3) | |
| rv = x.toFixed(Math.max(0, p, p1)); | |
| else | |
| rv = x.toExponential(p); | |
| return rv; | |
| }, | |
| f: function(x, p) { return x.toFixed(p); }, | |
| r: function(x, p) { return d3.round(x, p = d3_format_precision(x, p)).toFixed(Math.max(0, Math.min(20, p))); } | |
| }); | |
| function d3_format_precision(x, p) { | |
| return p - (x ? 1 + Math.floor(Math.log(x + Math.pow(10, 1 + Math.floor(Math.log(x) / Math.LN10) - p)) / Math.LN10) : 1); | |
| } | |
| d3.formatPrecision = function(x, p) { | |
| return d3_format_precision(x, p || 0); | |
| }; | |
| function d3_format_typeDefault(x) { | |
| return x + ""; | |
| } | |
| // Apply comma grouping for thousands. | |
| function d3_format_group(value) { | |
| var i = value.lastIndexOf("."), | |
| f = i >= 0 ? value.substring(i) : (i = value.length, ""), | |
| t = []; | |
| while (i > 0) t.push(value.substring(i -= 3, i + 3)); | |
| return t.reverse().join(",") + f; | |
| } | |
| var d3_formatPrefixes = ["y","z","a","f","p","n","μ","m","","k","M","G","T","P","E","Z","Y"].map(d3_formatPrefix); | |
| d3.formatPrefix = function(value, precision) { | |
| var i = 0; | |
| if (value) { | |
| if (value < 0) value *= -1; | |
| if (precision) value = d3.round(value, d3_format_precision(value, precision)); | |
| i = 1 + Math.floor(1e-12 + Math.log(value) / Math.LN10); | |
| i = Math.max(-24, Math.min(24, Math.floor((i <= 0 ? i + 1 : i - 1) / 3) * 3)); | |
| } | |
| return d3_formatPrefixes[8 + i / 3]; | |
| }; | |
| function d3_formatPrefix(d, i) { | |
| var k = Math.pow(10, Math.abs(8 - i) * 3); | |
| return { | |
| scale: i > 8 ? function(d) { return d / k; } : function(d) { return d * k; }, | |
| symbol: d | |
| }; | |
| } | |
| /* | |
| * TERMS OF USE - EASING EQUATIONS | |
| * | |
| * Open source under the BSD License. | |
| * | |
| * Copyright 2001 Robert Penner | |
| * All rights reserved. | |
| * | |
| * Redistribution and use in source and binary forms, with or without | |
| * modification, are permitted provided that the following conditions are met: | |
| * | |
| * - Redistributions of source code must retain the above copyright notice, this | |
| * list of conditions and the following disclaimer. | |
| * | |
| * - Redistributions in binary form must reproduce the above copyright notice, | |
| * this list of conditions and the following disclaimer in the documentation | |
| * and/or other materials provided with the distribution. | |
| * | |
| * - Neither the name of the author nor the names of contributors may be used to | |
| * endorse or promote products derived from this software without specific | |
| * prior written permission. | |
| * | |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |
| * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
| * POSSIBILITY OF SUCH DAMAGE. | |
| */ | |
| var d3_ease_quad = d3_ease_poly(2), | |
| d3_ease_cubic = d3_ease_poly(3), | |
| d3_ease_default = function() { return d3_ease_identity; }; | |
| var d3_ease = d3.map({ | |
| linear: d3_ease_default, | |
| poly: d3_ease_poly, | |
| quad: function() { return d3_ease_quad; }, | |
| cubic: function() { return d3_ease_cubic; }, | |
| sin: function() { return d3_ease_sin; }, | |
| exp: function() { return d3_ease_exp; }, | |
| circle: function() { return d3_ease_circle; }, | |
| elastic: d3_ease_elastic, | |
| back: d3_ease_back, | |
| bounce: function() { return d3_ease_bounce; } | |
| }); | |
| var d3_ease_mode = d3.map({ | |
| "in": d3_ease_identity, | |
| "out": d3_ease_reverse, | |
| "in-out": d3_ease_reflect, | |
| "out-in": function(f) { return d3_ease_reflect(d3_ease_reverse(f)); } | |
| }); | |
| d3.ease = function(name) { | |
| var i = name.indexOf("-"), | |
| t = i >= 0 ? name.substring(0, i) : name, | |
| m = i >= 0 ? name.substring(i + 1) : "in"; | |
| t = d3_ease.get(t) || d3_ease_default; | |
| m = d3_ease_mode.get(m) || d3_ease_identity; | |
| return d3_ease_clamp(m(t.apply(null, Array.prototype.slice.call(arguments, 1)))); | |
| }; | |
| function d3_ease_clamp(f) { | |
| return function(t) { | |
| return t <= 0 ? 0 : t >= 1 ? 1 : f(t); | |
| }; | |
| } | |
| function d3_ease_reverse(f) { | |
| return function(t) { | |
| return 1 - f(1 - t); | |
| }; | |
| } | |
| function d3_ease_reflect(f) { | |
| return function(t) { | |
| return .5 * (t < .5 ? f(2 * t) : (2 - f(2 - 2 * t))); | |
| }; | |
| } | |
| function d3_ease_identity(t) { | |
| return t; | |
| } | |
| function d3_ease_poly(e) { | |
| return function(t) { | |
| return Math.pow(t, e); | |
| }; | |
| } | |
| function d3_ease_sin(t) { | |
| return 1 - Math.cos(t * Math.PI / 2); | |
| } | |
| function d3_ease_exp(t) { | |
| return Math.pow(2, 10 * (t - 1)); | |
| } | |
| function d3_ease_circle(t) { | |
| return 1 - Math.sqrt(1 - t * t); | |
| } | |
| function d3_ease_elastic(a, p) { | |
| var s; | |
| if (arguments.length < 2) p = 0.45; | |
| if (arguments.length < 1) { a = 1; s = p / 4; } | |
| else s = p / (2 * Math.PI) * Math.asin(1 / a); | |
| return function(t) { | |
| return 1 + a * Math.pow(2, 10 * -t) * Math.sin((t - s) * 2 * Math.PI / p); | |
| }; | |
| } | |
| function d3_ease_back(s) { | |
| if (!s) s = 1.70158; | |
| return function(t) { | |
| return t * t * ((s + 1) * t - s); | |
| }; | |
| } | |
| function d3_ease_bounce(t) { | |
| return t < 1 / 2.75 ? 7.5625 * t * t | |
| : t < 2 / 2.75 ? 7.5625 * (t -= 1.5 / 2.75) * t + .75 | |
| : t < 2.5 / 2.75 ? 7.5625 * (t -= 2.25 / 2.75) * t + .9375 | |
| : 7.5625 * (t -= 2.625 / 2.75) * t + .984375; | |
| } | |
| d3.event = null; | |
| function d3_eventCancel() { | |
| d3.event.stopPropagation(); | |
| d3.event.preventDefault(); | |
| } | |
| function d3_eventSource() { | |
| var e = d3.event, s; | |
| while (s = e.sourceEvent) e = s; | |
| return e; | |
| } | |
| // Like d3.dispatch, but for custom events abstracting native UI events. These | |
| // events have a target component (such as a brush), a target element (such as | |
| // the svg:g element containing the brush) and the standard arguments `d` (the | |
| // target element's data) and `i` (the selection index of the target element). | |
| function d3_eventDispatch(target) { | |
| var dispatch = new d3_dispatch, | |
| i = 0, | |
| n = arguments.length; | |
| while (++i < n) dispatch[arguments[i]] = d3_dispatch_event(dispatch); | |
| // Creates a dispatch context for the specified `thiz` (typically, the target | |
| // DOM element that received the source event) and `argumentz` (typically, the | |
| // data `d` and index `i` of the target element). The returned function can be | |
| // used to dispatch an event to any registered listeners; the function takes a | |
| // single argument as input, being the event to dispatch. The event must have | |
| // a "type" attribute which corresponds to a type registered in the | |
| // constructor. This context will automatically populate the "sourceEvent" and | |
| // "target" attributes of the event, as well as setting the `d3.event` global | |
| // for the duration of the notification. | |
| dispatch.of = function(thiz, argumentz) { | |
| return function(e1) { | |
| try { | |
| var e0 = | |
| e1.sourceEvent = d3.event; | |
| e1.target = target; | |
| d3.event = e1; | |
| dispatch[e1.type].apply(thiz, argumentz); | |
| } finally { | |
| d3.event = e0; | |
| } | |
| }; | |
| }; | |
| return dispatch; | |
| } | |
| d3.transform = function(string) { | |
| var g = document.createElementNS(d3.ns.prefix.svg, "g"); | |
| return (d3.transform = function(string) { | |
| g.setAttribute("transform", string); | |
| var t = g.transform.baseVal.consolidate(); | |
| return new d3_transform(t ? t.matrix : d3_transformIdentity); | |
| })(string); | |
| }; | |
| // Compute x-scale and normalize the first row. | |
| // Compute shear and make second row orthogonal to first. | |
| // Compute y-scale and normalize the second row. | |
| // Finally, compute the rotation. | |
| function d3_transform(m) { | |
| var r0 = [m.a, m.b], | |
| r1 = [m.c, m.d], | |
| kx = d3_transformNormalize(r0), | |
| kz = d3_transformDot(r0, r1), | |
| ky = d3_transformNormalize(d3_transformCombine(r1, r0, -kz)) || 0; | |
| if (r0[0] * r1[1] < r1[0] * r0[1]) { | |
| r0[0] *= -1; | |
| r0[1] *= -1; | |
| kx *= -1; | |
| kz *= -1; | |
| } | |
| this.rotate = (kx ? Math.atan2(r0[1], r0[0]) : Math.atan2(-r1[0], r1[1])) * d3_transformDegrees; | |
| this.translate = [m.e, m.f]; | |
| this.scale = [kx, ky]; | |
| this.skew = ky ? Math.atan2(kz, ky) * d3_transformDegrees : 0; | |
| }; | |
| d3_transform.prototype.toString = function() { | |
| return "translate(" + this.translate | |
| + ")rotate(" + this.rotate | |
| + ")skewX(" + this.skew | |
| + ")scale(" + this.scale | |
| + ")"; | |
| }; | |
| function d3_transformDot(a, b) { | |
| return a[0] * b[0] + a[1] * b[1]; | |
| } | |
| function d3_transformNormalize(a) { | |
| var k = Math.sqrt(d3_transformDot(a, a)); | |
| if (k) { | |
| a[0] /= k; | |
| a[1] /= k; | |
| } | |
| return k; | |
| } | |
| function d3_transformCombine(a, b, k) { | |
| a[0] += k * b[0]; | |
| a[1] += k * b[1]; | |
| return a; | |
| } | |
| var d3_transformDegrees = 180 / Math.PI, | |
| d3_transformIdentity = {a: 1, b: 0, c: 0, d: 1, e: 0, f: 0}; | |
| d3.interpolate = function(a, b) { | |
| var i = d3.interpolators.length, f; | |
| while (--i >= 0 && !(f = d3.interpolators[i](a, b))); | |
| return f; | |
| }; | |
| d3.interpolateNumber = function(a, b) { | |
| b -= a; | |
| return function(t) { return a + b * t; }; | |
| }; | |
| d3.interpolateRound = function(a, b) { | |
| b -= a; | |
| return function(t) { return Math.round(a + b * t); }; | |
| }; | |
| d3.interpolateString = function(a, b) { | |
| var m, // current match | |
| i, // current index | |
| j, // current index (for coalescing) | |
| s0 = 0, // start index of current string prefix | |
| s1 = 0, // end index of current string prefix | |
| s = [], // string constants and placeholders | |
| q = [], // number interpolators | |
| n, // q.length | |
| o; | |
| // Reset our regular expression! | |
| d3_interpolate_number.lastIndex = 0; | |
| // Find all numbers in b. | |
| for (i = 0; m = d3_interpolate_number.exec(b); ++i) { | |
| if (m.index) s.push(b.substring(s0, s1 = m.index)); | |
| q.push({i: s.length, x: m[0]}); | |
| s.push(null); | |
| s0 = d3_interpolate_number.lastIndex; | |
| } | |
| if (s0 < b.length) s.push(b.substring(s0)); | |
| // Find all numbers in a. | |
| for (i = 0, n = q.length; (m = d3_interpolate_number.exec(a)) && i < n; ++i) { | |
| o = q[i]; | |
| if (o.x == m[0]) { // The numbers match, so coalesce. | |
| if (o.i) { | |
| if (s[o.i + 1] == null) { // This match is followed by another number. | |
| s[o.i - 1] += o.x; | |
| s.splice(o.i, 1); | |
| for (j = i + 1; j < n; ++j) q[j].i--; | |
| } else { // This match is followed by a string, so coalesce twice. | |
| s[o.i - 1] += o.x + s[o.i + 1]; | |
| s.splice(o.i, 2); | |
| for (j = i + 1; j < n; ++j) q[j].i -= 2; | |
| } | |
| } else { | |
| if (s[o.i + 1] == null) { // This match is followed by another number. | |
| s[o.i] = o.x; | |
| } else { // This match is followed by a string, so coalesce twice. | |
| s[o.i] = o.x + s[o.i + 1]; | |
| s.splice(o.i + 1, 1); | |
| for (j = i + 1; j < n; ++j) q[j].i--; | |
| } | |
| } | |
| q.splice(i, 1); | |
| n--; | |
| i--; | |
| } else { | |
| o.x = d3.interpolateNumber(parseFloat(m[0]), parseFloat(o.x)); | |
| } | |
| } | |
| // Remove any numbers in b not found in a. | |
| while (i < n) { | |
| o = q.pop(); | |
| if (s[o.i + 1] == null) { // This match is followed by another number. | |
| s[o.i] = o.x; | |
| } else { // This match is followed by a string, so coalesce twice. | |
| s[o.i] = o.x + s[o.i + 1]; | |
| s.splice(o.i + 1, 1); | |
| } | |
| n--; | |
| } | |
| // Special optimization for only a single match. | |
| if (s.length === 1) { | |
| return s[0] == null ? q[0].x : function() { return b; }; | |
| } | |
| // Otherwise, interpolate each of the numbers and rejoin the string. | |
| return function(t) { | |
| for (i = 0; i < n; ++i) s[(o = q[i]).i] = o.x(t); | |
| return s.join(""); | |
| }; | |
| }; | |
| d3.interpolateTransform = function(a, b) { | |
| var s = [], // string constants and placeholders | |
| q = [], // number interpolators | |
| n, | |
| A = d3.transform(a), | |
| B = d3.transform(b), | |
| ta = A.translate, | |
| tb = B.translate, | |
| ra = A.rotate, | |
| rb = B.rotate, | |
| wa = A.skew, | |
| wb = B.skew, | |
| ka = A.scale, | |
| kb = B.scale; | |
| if (ta[0] != tb[0] || ta[1] != tb[1]) { | |
| s.push("translate(", null, ",", null, ")"); | |
| q.push({i: 1, x: d3.interpolateNumber(ta[0], tb[0])}, {i: 3, x: d3.interpolateNumber(ta[1], tb[1])}); | |
| } else if (tb[0] || tb[1]) { | |
| s.push("translate(" + tb + ")"); | |
| } else { | |
| s.push(""); | |
| } | |
| if (ra != rb) { | |
| if (ra - rb > 180) rb += 360; else if (rb - ra > 180) ra += 360; // shortest path | |
| q.push({i: s.push(s.pop() + "rotate(", null, ")") - 2, x: d3.interpolateNumber(ra, rb)}); | |
| } else if (rb) { | |
| s.push(s.pop() + "rotate(" + rb + ")"); | |
| } | |
| if (wa != wb) { | |
| q.push({i: s.push(s.pop() + "skewX(", null, ")") - 2, x: d3.interpolateNumber(wa, wb)}); | |
| } else if (wb) { | |
| s.push(s.pop() + "skewX(" + wb + ")"); | |
| } | |
| if (ka[0] != kb[0] || ka[1] != kb[1]) { | |
| n = s.push(s.pop() + "scale(", null, ",", null, ")"); | |
| q.push({i: n - 4, x: d3.interpolateNumber(ka[0], kb[0])}, {i: n - 2, x: d3.interpolateNumber(ka[1], kb[1])}); | |
| } else if (kb[0] != 1 || kb[1] != 1) { | |
| s.push(s.pop() + "scale(" + kb + ")"); | |
| } | |
| n = q.length; | |
| return function(t) { | |
| var i = -1, o; | |
| while (++i < n) s[(o = q[i]).i] = o.x(t); | |
| return s.join(""); | |
| }; | |
| }; | |
| d3.interpolateRgb = function(a, b) { | |
| a = d3.rgb(a); | |
| b = d3.rgb(b); | |
| var ar = a.r, | |
| ag = a.g, | |
| ab = a.b, | |
| br = b.r - ar, | |
| bg = b.g - ag, | |
| bb = b.b - ab; | |
| return function(t) { | |
| return "#" | |
| + d3_rgb_hex(Math.round(ar + br * t)) | |
| + d3_rgb_hex(Math.round(ag + bg * t)) | |
| + d3_rgb_hex(Math.round(ab + bb * t)); | |
| }; | |
| }; | |
| // interpolates HSL space, but outputs RGB string (for compatibility) | |
| d3.interpolateHsl = function(a, b) { | |
| a = d3.hsl(a); | |
| b = d3.hsl(b); | |
| var h0 = a.h, | |
| s0 = a.s, | |
| l0 = a.l, | |
| h1 = b.h - h0, | |
| s1 = b.s - s0, | |
| l1 = b.l - l0; | |
| if (h1 > 180) h1 -= 360; else if (h1 < -180) h1 += 360; // shortest path | |
| return function(t) { | |
| return d3_hsl_rgb(h0 + h1 * t, s0 + s1 * t, l0 + l1 * t) + ""; | |
| }; | |
| }; | |
| d3.interpolateLab = function(a, b) { | |
| a = d3.lab(a); | |
| b = d3.lab(b); | |
| var al = a.l, | |
| aa = a.a, | |
| ab = a.b, | |
| bl = b.l - al, | |
| ba = b.a - aa, | |
| bb = b.b - ab; | |
| return function(t) { | |
| return d3_lab_rgb(al + bl * t, aa + ba * t, ab + bb * t) + ""; | |
| }; | |
| }; | |
| d3.interpolateHcl = function(a, b) { | |
| a = d3.hcl(a); | |
| b = d3.hcl(b); | |
| var ah = a.h, | |
| ac = a.c, | |
| al = a.l, | |
| bh = b.h - ah, | |
| bc = b.c - ac, | |
| bl = b.l - al; | |
| if (bh > 180) bh -= 360; else if (bh < -180) bh += 360; // shortest path | |
| return function(t) { | |
| return d3_hcl_lab(ah + bh * t, ac + bc * t, al + bl * t) + ""; | |
| }; | |
| }; | |
| d3.interpolateArray = function(a, b) { | |
| var x = [], | |
| c = [], | |
| na = a.length, | |
| nb = b.length, | |
| n0 = Math.min(a.length, b.length), | |
| i; | |
| for (i = 0; i < n0; ++i) x.push(d3.interpolate(a[i], b[i])); | |
| for (; i < na; ++i) c[i] = a[i]; | |
| for (; i < nb; ++i) c[i] = b[i]; | |
| return function(t) { | |
| for (i = 0; i < n0; ++i) c[i] = x[i](t); | |
| return c; | |
| }; | |
| }; | |
| d3.interpolateObject = function(a, b) { | |
| var i = {}, | |
| c = {}, | |
| k; | |
| for (k in a) { | |
| if (k in b) { | |
| i[k] = d3_interpolateByName(k)(a[k], b[k]); | |
| } else { | |
| c[k] = a[k]; | |
| } | |
| } | |
| for (k in b) { | |
| if (!(k in a)) { | |
| c[k] = b[k]; | |
| } | |
| } | |
| return function(t) { | |
| for (k in i) c[k] = i[k](t); | |
| return c; | |
| }; | |
| }; | |
| var d3_interpolate_number = /[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?/g; | |
| function d3_interpolateByName(name) { | |
| return name == "transform" | |
| ? d3.interpolateTransform | |
| : d3.interpolate; | |
| } | |
| d3.interpolators = [ | |
| d3.interpolateObject, | |
| function(a, b) { return b instanceof Array && d3.interpolateArray(a, b); }, | |
| function(a, b) { return (typeof a === "string" || typeof b === "string") && d3.interpolateString(a + "", b + ""); }, | |
| function(a, b) { return (typeof b === "string" ? d3_rgb_names.has(b) || /^(#|rgb\(|hsl\()/.test(b) : b instanceof d3_Rgb || b instanceof d3_Hsl) && d3.interpolateRgb(a, b); }, | |
| function(a, b) { return !isNaN(a = +a) && !isNaN(b = +b) && d3.interpolateNumber(a, b); } | |
| ]; | |
| function d3_uninterpolateNumber(a, b) { | |
| b = b - (a = +a) ? 1 / (b - a) : 0; | |
| return function(x) { return (x - a) * b; }; | |
| } | |
| function d3_uninterpolateClamp(a, b) { | |
| b = b - (a = +a) ? 1 / (b - a) : 0; | |
| return function(x) { return Math.max(0, Math.min(1, (x - a) * b)); }; | |
| } | |
| d3.rgb = function(r, g, b) { | |
| return arguments.length === 1 | |
| ? (r instanceof d3_Rgb ? d3_rgb(r.r, r.g, r.b) | |
| : d3_rgb_parse("" + r, d3_rgb, d3_hsl_rgb)) | |
| : d3_rgb(~~r, ~~g, ~~b); | |
| }; | |
| function d3_rgb(r, g, b) { | |
| return new d3_Rgb(r, g, b); | |
| } | |
| function d3_Rgb(r, g, b) { | |
| this.r = r; | |
| this.g = g; | |
| this.b = b; | |
| } | |
| d3_Rgb.prototype.brighter = function(k) { | |
| k = Math.pow(0.7, arguments.length ? k : 1); | |
| var r = this.r, | |
| g = this.g, | |
| b = this.b, | |
| i = 30; | |
| if (!r && !g && !b) return d3_rgb(i, i, i); | |
| if (r && r < i) r = i; | |
| if (g && g < i) g = i; | |
| if (b && b < i) b = i; | |
| return d3_rgb( | |
| Math.min(255, Math.floor(r / k)), | |
| Math.min(255, Math.floor(g / k)), | |
| Math.min(255, Math.floor(b / k))); | |
| }; | |
| d3_Rgb.prototype.darker = function(k) { | |
| k = Math.pow(0.7, arguments.length ? k : 1); | |
| return d3_rgb( | |
| Math.floor(k * this.r), | |
| Math.floor(k * this.g), | |
| Math.floor(k * this.b)); | |
| }; | |
| d3_Rgb.prototype.hsl = function() { | |
| return d3_rgb_hsl(this.r, this.g, this.b); | |
| }; | |
| d3_Rgb.prototype.toString = function() { | |
| return "#" + d3_rgb_hex(this.r) + d3_rgb_hex(this.g) + d3_rgb_hex(this.b); | |
| }; | |
| function d3_rgb_hex(v) { | |
| return v < 0x10 | |
| ? "0" + Math.max(0, v).toString(16) | |
| : Math.min(255, v).toString(16); | |
| } | |
| function d3_rgb_parse(format, rgb, hsl) { | |
| var r = 0, // red channel; int in [0, 255] | |
| g = 0, // green channel; int in [0, 255] | |
| b = 0, // blue channel; int in [0, 255] | |
| m1, // CSS color specification match | |
| m2, // CSS color specification type (e.g., rgb) | |
| name; | |
| /* Handle hsl, rgb. */ | |
| m1 = /([a-z]+)\((.*)\)/i.exec(format); | |
| if (m1) { | |
| m2 = m1[2].split(","); | |
| switch (m1[1]) { | |
| case "hsl": { | |
| return hsl( | |
| parseFloat(m2[0]), // degrees | |
| parseFloat(m2[1]) / 100, // percentage | |
| parseFloat(m2[2]) / 100 // percentage | |
| ); | |
| } | |
| case "rgb": { | |
| return rgb( | |
| d3_rgb_parseNumber(m2[0]), | |
| d3_rgb_parseNumber(m2[1]), | |
| d3_rgb_parseNumber(m2[2]) | |
| ); | |
| } | |
| } | |
| } | |
| /* Named colors. */ | |
| if (name = d3_rgb_names.get(format)) return rgb(name.r, name.g, name.b); | |
| /* Hexadecimal colors: #rgb and #rrggbb. */ | |
| if (format != null && format.charAt(0) === "#") { | |
| if (format.length === 4) { | |
| r = format.charAt(1); r += r; | |
| g = format.charAt(2); g += g; | |
| b = format.charAt(3); b += b; | |
| } else if (format.length === 7) { | |
| r = format.substring(1, 3); | |
| g = format.substring(3, 5); | |
| b = format.substring(5, 7); | |
| } | |
| r = parseInt(r, 16); | |
| g = parseInt(g, 16); | |
| b = parseInt(b, 16); | |
| } | |
| return rgb(r, g, b); | |
| } | |
| function d3_rgb_hsl(r, g, b) { | |
| var min = Math.min(r /= 255, g /= 255, b /= 255), | |
| max = Math.max(r, g, b), | |
| d = max - min, | |
| h, | |
| s, | |
| l = (max + min) / 2; | |
| if (d) { | |
| s = l < .5 ? d / (max + min) : d / (2 - max - min); | |
| if (r == max) h = (g - b) / d + (g < b ? 6 : 0); | |
| else if (g == max) h = (b - r) / d + 2; | |
| else h = (r - g) / d + 4; | |
| h *= 60; | |
| } else { | |
| s = h = 0; | |
| } | |
| return d3_hsl(h, s, l); | |
| } | |
| function d3_rgb_lab(r, g, b) { | |
| r = d3_rgb_xyz(r); | |
| g = d3_rgb_xyz(g); | |
| b = d3_rgb_xyz(b); | |
| var x = d3_xyz_lab((0.4124564 * r + 0.3575761 * g + 0.1804375 * b) / d3_lab_X), | |
| y = d3_xyz_lab((0.2126729 * r + 0.7151522 * g + 0.0721750 * b) / d3_lab_Y), | |
| z = d3_xyz_lab((0.0193339 * r + 0.1191920 * g + 0.9503041 * b) / d3_lab_Z); | |
| return d3_lab(116 * y - 16, 500 * (x - y), 200 * (y - z)); | |
| } | |
| function d3_rgb_xyz(r) { | |
| return (r /= 255) <= 0.04045 ? r / 12.92 : Math.pow((r + 0.055) / 1.055, 2.4); | |
| } | |
| function d3_rgb_parseNumber(c) { // either integer or percentage | |
| var f = parseFloat(c); | |
| return c.charAt(c.length - 1) === "%" ? Math.round(f * 2.55) : f; | |
| } | |
| var d3_rgb_names = d3.map({ | |
| aliceblue: "#f0f8ff", | |
| antiquewhite: "#faebd7", | |
| aqua: "#00ffff", | |
| aquamarine: "#7fffd4", | |
| azure: "#f0ffff", | |
| beige: "#f5f5dc", | |
| bisque: "#ffe4c4", | |
| black: "#000000", | |
| blanchedalmond: "#ffebcd", | |
| blue: "#0000ff", | |
| blueviolet: "#8a2be2", | |
| brown: "#a52a2a", | |
| burlywood: "#deb887", | |
| cadetblue: "#5f9ea0", | |
| chartreuse: "#7fff00", | |
| chocolate: "#d2691e", | |
| coral: "#ff7f50", | |
| cornflowerblue: "#6495ed", | |
| cornsilk: "#fff8dc", | |
| crimson: "#dc143c", | |
| cyan: "#00ffff", | |
| darkblue: "#00008b", | |
| darkcyan: "#008b8b", | |
| darkgoldenrod: "#b8860b", | |
| darkgray: "#a9a9a9", | |
| darkgreen: "#006400", | |
| darkgrey: "#a9a9a9", | |
| darkkhaki: "#bdb76b", | |
| darkmagenta: "#8b008b", | |
| darkolivegreen: "#556b2f", | |
| darkorange: "#ff8c00", | |
| darkorchid: "#9932cc", | |
| darkred: "#8b0000", | |
| darksalmon: "#e9967a", | |
| darkseagreen: "#8fbc8f", | |
| darkslateblue: "#483d8b", | |
| darkslategray: "#2f4f4f", | |
| darkslategrey: "#2f4f4f", | |
| darkturquoise: "#00ced1", | |
| darkviolet: "#9400d3", | |
| deeppink: "#ff1493", | |
| deepskyblue: "#00bfff", | |
| dimgray: "#696969", | |
| dimgrey: "#696969", | |
| dodgerblue: "#1e90ff", | |
| firebrick: "#b22222", | |
| floralwhite: "#fffaf0", | |
| forestgreen: "#228b22", | |
| fuchsia: "#ff00ff", | |
| gainsboro: "#dcdcdc", | |
| ghostwhite: "#f8f8ff", | |
| gold: "#ffd700", | |
| goldenrod: "#daa520", | |
| gray: "#808080", | |
| green: "#008000", | |
| greenyellow: "#adff2f", | |
| grey: "#808080", | |
| honeydew: "#f0fff0", | |
| hotpink: "#ff69b4", | |
| indianred: "#cd5c5c", | |
| indigo: "#4b0082", | |
| ivory: "#fffff0", | |
| khaki: "#f0e68c", | |
| lavender: "#e6e6fa", | |
| lavenderblush: "#fff0f5", | |
| lawngreen: "#7cfc00", | |
| lemonchiffon: "#fffacd", | |
| lightblue: "#add8e6", | |
| lightcoral: "#f08080", | |
| lightcyan: "#e0ffff", | |
| lightgoldenrodyellow: "#fafad2", | |
| lightgray: "#d3d3d3", | |
| lightgreen: "#90ee90", | |
| lightgrey: "#d3d3d3", | |
| lightpink: "#ffb6c1", | |
| lightsalmon: "#ffa07a", | |
| lightseagreen: "#20b2aa", | |
| lightskyblue: "#87cefa", | |
| lightslategray: "#778899", | |
| lightslategrey: "#778899", | |
| lightsteelblue: "#b0c4de", | |
| lightyellow: "#ffffe0", | |
| lime: "#00ff00", | |
| limegreen: "#32cd32", | |
| linen: "#faf0e6", | |
| magenta: "#ff00ff", | |
| maroon: "#800000", | |
| mediumaquamarine: "#66cdaa", | |
| mediumblue: "#0000cd", | |
| mediumorchid: "#ba55d3", | |
| mediumpurple: "#9370db", | |
| mediumseagreen: "#3cb371", | |
| mediumslateblue: "#7b68ee", | |
| mediumspringgreen: "#00fa9a", | |
| mediumturquoise: "#48d1cc", | |
| mediumvioletred: "#c71585", | |
| midnightblue: "#191970", | |
| mintcream: "#f5fffa", | |
| mistyrose: "#ffe4e1", | |
| moccasin: "#ffe4b5", | |
| navajowhite: "#ffdead", | |
| navy: "#000080", | |
| oldlace: "#fdf5e6", | |
| olive: "#808000", | |
| olivedrab: "#6b8e23", | |
| orange: "#ffa500", | |
| orangered: "#ff4500", | |
| orchid: "#da70d6", | |
| palegoldenrod: "#eee8aa", | |
| palegreen: "#98fb98", | |
| paleturquoise: "#afeeee", | |
| palevioletred: "#db7093", | |
| papayawhip: "#ffefd5", | |
| peachpuff: "#ffdab9", | |
| peru: "#cd853f", | |
| pink: "#ffc0cb", | |
| plum: "#dda0dd", | |
| powderblue: "#b0e0e6", | |
| purple: "#800080", | |
| red: "#ff0000", | |
| rosybrown: "#bc8f8f", | |
| royalblue: "#4169e1", | |
| saddlebrown: "#8b4513", | |
| salmon: "#fa8072", | |
| sandybrown: "#f4a460", | |
| seagreen: "#2e8b57", | |
| seashell: "#fff5ee", | |
| sienna: "#a0522d", | |
| silver: "#c0c0c0", | |
| skyblue: "#87ceeb", | |
| slateblue: "#6a5acd", | |
| slategray: "#708090", | |
| slategrey: "#708090", | |
| snow: "#fffafa", | |
| springgreen: "#00ff7f", | |
| steelblue: "#4682b4", | |
| tan: "#d2b48c", | |
| teal: "#008080", | |
| thistle: "#d8bfd8", | |
| tomato: "#ff6347", | |
| turquoise: "#40e0d0", | |
| violet: "#ee82ee", | |
| wheat: "#f5deb3", | |
| white: "#ffffff", | |
| whitesmoke: "#f5f5f5", | |
| yellow: "#ffff00", | |
| yellowgreen: "#9acd32" | |
| }); | |
| d3_rgb_names.forEach(function(key, value) { | |
| d3_rgb_names.set(key, d3_rgb_parse(value, d3_rgb, d3_hsl_rgb)); | |
| }); | |
| d3.hsl = function(h, s, l) { | |
| return arguments.length === 1 | |
| ? (h instanceof d3_Hsl ? d3_hsl(h.h, h.s, h.l) | |
| : d3_rgb_parse("" + h, d3_rgb_hsl, d3_hsl)) | |
| : d3_hsl(+h, +s, +l); | |
| }; | |
| function d3_hsl(h, s, l) { | |
| return new d3_Hsl(h, s, l); | |
| } | |
| function d3_Hsl(h, s, l) { | |
| this.h = h; | |
| this.s = s; | |
| this.l = l; | |
| } | |
| d3_Hsl.prototype.brighter = function(k) { | |
| k = Math.pow(0.7, arguments.length ? k : 1); | |
| return d3_hsl(this.h, this.s, this.l / k); | |
| }; | |
| d3_Hsl.prototype.darker = function(k) { | |
| k = Math.pow(0.7, arguments.length ? k : 1); | |
| return d3_hsl(this.h, this.s, k * this.l); | |
| }; | |
| d3_Hsl.prototype.rgb = function() { | |
| return d3_hsl_rgb(this.h, this.s, this.l); | |
| }; | |
| d3_Hsl.prototype.toString = function() { | |
| return this.rgb().toString(); | |
| }; | |
| function d3_hsl_rgb(h, s, l) { | |
| var m1, | |
| m2; | |
| /* Some simple corrections for h, s and l. */ | |
| h = h % 360; if (h < 0) h += 360; | |
| s = s < 0 ? 0 : s > 1 ? 1 : s; | |
| l = l < 0 ? 0 : l > 1 ? 1 : l; | |
| /* From FvD 13.37, CSS Color Module Level 3 */ | |
| m2 = l <= .5 ? l * (1 + s) : l + s - l * s; | |
| m1 = 2 * l - m2; | |
| function v(h) { | |
| if (h > 360) h -= 360; | |
| else if (h < 0) h += 360; | |
| if (h < 60) return m1 + (m2 - m1) * h / 60; | |
| if (h < 180) return m2; | |
| if (h < 240) return m1 + (m2 - m1) * (240 - h) / 60; | |
| return m1; | |
| } | |
| function vv(h) { | |
| return Math.round(v(h) * 255); | |
| } | |
| return d3_rgb(vv(h + 120), vv(h), vv(h - 120)); | |
| } | |
| d3.hcl = function(h, c, l) { | |
| return arguments.length === 1 | |
| ? (h instanceof d3_Hcl ? d3_hcl(h.h, h.c, h.l) | |
| : (h instanceof d3_Lab ? d3_lab_hcl(h.l, h.a, h.b) | |
| : d3_lab_hcl((h = d3_rgb_lab((h = d3.rgb(h)).r, h.g, h.b)).l, h.a, h.b))) | |
| : d3_hcl(+h, +c, +l); | |
| }; | |
| function d3_hcl(h, c, l) { | |
| return new d3_Hcl(h, c, l); | |
| } | |
| function d3_Hcl(h, c, l) { | |
| this.h = h; | |
| this.c = c; | |
| this.l = l; | |
| } | |
| d3_Hcl.prototype.brighter = function(k) { | |
| return d3_hcl(this.h, this.c, Math.min(100, this.l + d3_lab_K * (arguments.length ? k : 1))); | |
| }; | |
| d3_Hcl.prototype.darker = function(k) { | |
| return d3_hcl(this.h, this.c, Math.max(0, this.l - d3_lab_K * (arguments.length ? k : 1))); | |
| }; | |
| d3_Hcl.prototype.rgb = function() { | |
| return d3_hcl_lab(this.h, this.c, this.l).rgb(); | |
| }; | |
| d3_Hcl.prototype.toString = function() { | |
| return this.rgb() + ""; | |
| }; | |
| function d3_hcl_lab(h, c, l) { | |
| return d3_lab(l, Math.cos(h *= Math.PI / 180) * c, Math.sin(h) * c); | |
| } | |
| d3.lab = function(l, a, b) { | |
| return arguments.length === 1 | |
| ? (l instanceof d3_Lab ? d3_lab(l.l, l.a, l.b) | |
| : (l instanceof d3_Hcl ? d3_hcl_lab(l.l, l.c, l.h) | |
| : d3_rgb_lab((l = d3.rgb(l)).r, l.g, l.b))) | |
| : d3_lab(+l, +a, +b); | |
| }; | |
| function d3_lab(l, a, b) { | |
| return new d3_Lab(l, a, b); | |
| } | |
| function d3_Lab(l, a, b) { | |
| this.l = l; | |
| this.a = a; | |
| this.b = b; | |
| } | |
| // Corresponds roughly to RGB brighter/darker | |
| var d3_lab_K = 18; | |
| // D65 standard referent | |
| var d3_lab_X = 0.950470, | |
| d3_lab_Y = 1, | |
| d3_lab_Z = 1.088830; | |
| d3_Lab.prototype.brighter = function(k) { | |
| return d3_lab(Math.min(100, this.l + d3_lab_K * (arguments.length ? k : 1)), this.a, this.b); | |
| }; | |
| d3_Lab.prototype.darker = function(k) { | |
| return d3_lab(Math.max(0, this.l - d3_lab_K * (arguments.length ? k : 1)), this.a, this.b); | |
| }; | |
| d3_Lab.prototype.rgb = function() { | |
| return d3_lab_rgb(this.l, this.a, this.b); | |
| }; | |
| d3_Lab.prototype.toString = function() { | |
| return this.rgb() + ""; | |
| }; | |
| function d3_lab_rgb(l, a, b) { | |
| var y = (l + 16) / 116, | |
| x = y + a / 500, | |
| z = y - b / 200; | |
| x = d3_lab_xyz(x) * d3_lab_X; | |
| y = d3_lab_xyz(y) * d3_lab_Y; | |
| z = d3_lab_xyz(z) * d3_lab_Z; | |
| return d3_rgb( | |
| d3_xyz_rgb( 3.2404542 * x - 1.5371385 * y - 0.4985314 * z), | |
| d3_xyz_rgb(-0.9692660 * x + 1.8760108 * y + 0.0415560 * z), | |
| d3_xyz_rgb( 0.0556434 * x - 0.2040259 * y + 1.0572252 * z) | |
| ); | |
| } | |
| function d3_lab_hcl(l, a, b) { | |
| return d3_hcl(Math.atan2(b, a) / Math.PI * 180, Math.sqrt(a * a + b * b), l); | |
| } | |
| function d3_lab_xyz(x) { | |
| return x > 0.206893034 ? x * x * x : (x - 4 / 29) / 7.787037; | |
| } | |
| function d3_xyz_lab(x) { | |
| return x > 0.008856 ? Math.pow(x, 1 / 3) : 7.787037 * x + 4 / 29; | |
| } | |
| function d3_xyz_rgb(r) { | |
| return Math.round(255 * (r <= 0.00304 ? 12.92 * r : 1.055 * Math.pow(r, 1 / 2.4) - 0.055)); | |
| } | |
| function d3_selection(groups) { | |
| d3_arraySubclass(groups, d3_selectionPrototype); | |
| groups.enter = groups.exit = function() { return d3.select(); }; | |
| return groups; | |
| } | |
| var d3_select = function(s, n) { return n.querySelector(s); }, | |
| d3_selectAll = function(s, n) { return n.querySelectorAll(s); }, | |
| d3_selectRoot = document.documentElement, | |
| d3_selectMatcher = d3_selectRoot.matchesSelector || d3_selectRoot.webkitMatchesSelector || d3_selectRoot.mozMatchesSelector || d3_selectRoot.msMatchesSelector || d3_selectRoot.oMatchesSelector, | |
| d3_selectMatches = function(n, s) { return d3_selectMatcher.call(n, s); }; | |
| // Prefer Sizzle, if available. | |
| if (typeof Sizzle === "function") { | |
| d3_select = function(s, n) { return Sizzle(s, n)[0] || null; }; | |
| d3_selectAll = function(s, n) { return Sizzle.uniqueSort(Sizzle(s, n)); }; | |
| d3_selectMatches = Sizzle.matchesSelector; | |
| } | |
| var d3_selectionPrototype = []; | |
| d3.selection = function() { | |
| return d3_selectionRoot; | |
| }; | |
| d3.selection.prototype = d3_selectionPrototype; | |
| d3_selectionPrototype.select = function(selector) { | |
| var subgroups = [], | |
| subgroup, | |
| subnode, | |
| group, | |
| node; | |
| if (typeof selector !== "function") selector = d3_selection_selector(selector); | |
| for (var j = -1, m = this.length; ++j < m;) { | |
| subgroups.push(subgroup = []); | |
| subgroup.parentNode = (group = this[j]).parentNode; | |
| for (var i = -1, n = group.length; ++i < n;) { | |
| if (node = group[i]) { | |
| subgroup.push(subnode = selector.call(node, node.__data__, i)); | |
| if (subnode && "__data__" in node) subnode.__data__ = node.__data__; | |
| } else { | |
| subgroup.push(null); | |
| } | |
| } | |
| } | |
| return d3_selection(subgroups); | |
| }; | |
| function d3_selection_selector(selector) { | |
| return function() { | |
| return d3_select(selector, this); | |
| }; | |
| } | |
| d3_selectionPrototype.selectAll = function(selector) { | |
| var subgroups = [], | |
| subgroup, | |
| node; | |
| if (typeof selector !== "function") selector = d3_selection_selectorAll(selector); | |
| for (var j = -1, m = this.length; ++j < m;) { | |
| for (var group = this[j], i = -1, n = group.length; ++i < n;) { | |
| if (node = group[i]) { | |
| subgroups.push(subgroup = d3_array(selector.call(node, node.__data__, i))); | |
| subgroup.parentNode = node; | |
| } | |
| } | |
| } | |
| return d3_selection(subgroups); | |
| }; | |
| function d3_selection_selectorAll(selector) { | |
| return function() { | |
| return d3_selectAll(selector, this); | |
| }; | |
| } | |
| d3_selectionPrototype.attr = function(name, value) { | |
| if (arguments.length < 2) { | |
| // For attr(string), return the attribute value for the first node. | |
| if (typeof name === "string") { | |
| var node = this.node(); | |
| name = d3.ns.qualify(name); | |
| return name.local | |
| ? node.getAttributeNS(name.space, name.local) | |
| : node.getAttribute(name); | |
| } | |
| // For attr(object), the object specifies the names and values of the | |
| // attributes to set or remove. The values may be functions that are | |
| // evaluated for each element. | |
| for (value in name) this.each(d3_selection_attr(value, name[value])); | |
| return this; | |
| } | |
| return this.each(d3_selection_attr(name, value)); | |
| }; | |
| function d3_selection_attr(name, value) { | |
| name = d3.ns.qualify(name); | |
| // For attr(string, null), remove the attribute with the specified name. | |
| function attrNull() { | |
| this.removeAttribute(name); | |
| } | |
| function attrNullNS() { | |
| this.removeAttributeNS(name.space, name.local); | |
| } | |
| // For attr(string, string), set the attribute with the specified name. | |
| function attrConstant() { | |
| this.setAttribute(name, value); | |
| } | |
| function attrConstantNS() { | |
| this.setAttributeNS(name.space, name.local, value); | |
| } | |
| // For attr(string, function), evaluate the function for each element, and set | |
| // or remove the attribute as appropriate. | |
| function attrFunction() { | |
| var x = value.apply(this, arguments); | |
| if (x == null) this.removeAttribute(name); | |
| else this.setAttribute(name, x); | |
| } | |
| function attrFunctionNS() { | |
| var x = value.apply(this, arguments); | |
| if (x == null) this.removeAttributeNS(name.space, name.local); | |
| else this.setAttributeNS(name.space, name.local, x); | |
| } | |
| return value == null | |
| ? (name.local ? attrNullNS : attrNull) : (typeof value === "function" | |
| ? (name.local ? attrFunctionNS : attrFunction) | |
| : (name.local ? attrConstantNS : attrConstant)); | |
| } | |
| d3_selectionPrototype.classed = function(name, value) { | |
| if (arguments.length < 2) { | |
| // For classed(string), return true only if the first node has the specified | |
| // class or classes. Note that even if the browser supports DOMTokenList, it | |
| // probably doesn't support it on SVG elements (which can be animated). | |
| if (typeof name === "string") { | |
| var node = this.node(), | |
| n = (name = name.trim().split(/^|\s+/g)).length, | |
| i = -1; | |
| if (value = node.classList) { | |
| while (++i < n) if (!value.contains(name[i])) return false; | |
| } else { | |
| value = node.className; | |
| if (value.baseVal != null) value = value.baseVal; | |
| while (++i < n) if (!d3_selection_classedRe(name[i]).test(value)) return false; | |
| } | |
| return true; | |
| } | |
| // For classed(object), the object specifies the names of classes to add or | |
| // remove. The values may be functions that are evaluated for each element. | |
| for (value in name) this.each(d3_selection_classed(value, name[value])); | |
| return this; | |
| } | |
| // Otherwise, both a name and a value are specified, and are handled as below. | |
| return this.each(d3_selection_classed(name, value)); | |
| }; | |
| function d3_selection_classedRe(name) { | |
| return new RegExp("(?:^|\\s+)" + d3.requote(name) + "(?:\\s+|$)", "g"); | |
| } | |
| // Multiple class names are allowed (e.g., "foo bar"). | |
| function d3_selection_classed(name, value) { | |
| name = name.trim().split(/\s+/).map(d3_selection_classedName); | |
| var n = name.length; | |
| function classedConstant() { | |
| var i = -1; | |
| while (++i < n) name[i](this, value); | |
| } | |
| // When the value is a function, the function is still evaluated only once per | |
| // element even if there are multiple class names. | |
| function classedFunction() { | |
| var i = -1, x = value.apply(this, arguments); | |
| while (++i < n) name[i](this, x); | |
| } | |
| return typeof value === "function" | |
| ? classedFunction | |
| : classedConstant; | |
| } | |
| function d3_selection_classedName(name) { | |
| var re = d3_selection_classedRe(name); | |
| return function(node, value) { | |
| if (c = node.classList) return value ? c.add(name) : c.remove(name); | |
| var c = node.className, | |
| cb = c.baseVal != null, | |
| cv = cb ? c.baseVal : c; | |
| if (value) { | |
| re.lastIndex = 0; | |
| if (!re.test(cv)) { | |
| cv = d3_collapse(cv + " " + name); | |
| if (cb) c.baseVal = cv; | |
| else node.className = cv; | |
| } | |
| } else if (cv) { | |
| cv = d3_collapse(cv.replace(re, " ")); | |
| if (cb) c.baseVal = cv; | |
| else node.className = cv; | |
| } | |
| }; | |
| } | |
| d3_selectionPrototype.style = function(name, value, priority) { | |
| var n = arguments.length; | |
| if (n < 3) { | |
| // For style(object) or style(object, string), the object specifies the | |
| // names and values of the attributes to set or remove. The values may be | |
| // functions that are evaluated for each element. The optional string | |
| // specifies the priority. | |
| if (typeof name !== "string") { | |
| if (n < 2) value = ""; | |
| for (priority in name) this.each(d3_selection_style(priority, name[priority], value)); | |
| return this; | |
| } | |
| // For style(string), return the computed style value for the first node. | |
| if (n < 2) return window | |
| .getComputedStyle(this.node(), null) | |
| .getPropertyValue(name); | |
| // For style(string, string) or style(string, function), use the default | |
| // priority. The priority is ignored for style(string, null). | |
| priority = ""; | |
| } | |
| // Otherwise, a name, value and priority are specified, and handled as below. | |
| return this.each(d3_selection_style(name, value, priority)); | |
| }; | |
| function d3_selection_style(name, value, priority) { | |
| // For style(name, null) or style(name, null, priority), remove the style | |
| // property with the specified name. The priority is ignored. | |
| function styleNull() { | |
| this.style.removeProperty(name); | |
| } | |
| // For style(name, string) or style(name, string, priority), set the style | |
| // property with the specified name, using the specified priority. | |
| function styleConstant() { | |
| this.style.setProperty(name, value, priority); | |
| } | |
| // For style(name, function) or style(name, function, priority), evaluate the | |
| // function for each element, and set or remove the style property as | |
| // appropriate. When setting, use the specified priority. | |
| function styleFunction() { | |
| var x = value.apply(this, arguments); | |
| if (x == null) this.style.removeProperty(name); | |
| else this.style.setProperty(name, x, priority); | |
| } | |
| return value == null | |
| ? styleNull : (typeof value === "function" | |
| ? styleFunction : styleConstant); | |
| } | |
| d3_selectionPrototype.property = function(name, value) { | |
| if (arguments.length < 2) { | |
| // For property(string), return the property value for the first node. | |
| if (typeof name === "string") return this.node()[name]; | |
| // For property(object), the object specifies the names and values of the | |
| // properties to set or remove. The values may be functions that are | |
| // evaluated for each element. | |
| for (value in name) this.each(d3_selection_property(value, name[value])); | |
| return this; | |
| } | |
| // Otherwise, both a name and a value are specified, and are handled as below. | |
| return this.each(d3_selection_property(name, value)); | |
| }; | |
| function d3_selection_property(name, value) { | |
| // For property(name, null), remove the property with the specified name. | |
| function propertyNull() { | |
| delete this[name]; | |
| } | |
| // For property(name, string), set the property with the specified name. | |
| function propertyConstant() { | |
| this[name] = value; | |
| } | |
| // For property(name, function), evaluate the function for each element, and | |
| // set or remove the property as appropriate. | |
| function propertyFunction() { | |
| var x = value.apply(this, arguments); | |
| if (x == null) delete this[name]; | |
| else this[name] = x; | |
| } | |
| return value == null | |
| ? propertyNull : (typeof value === "function" | |
| ? propertyFunction : propertyConstant); | |
| } | |
| d3_selectionPrototype.text = function(value) { | |
| return arguments.length < 1 | |
| ? this.node().textContent : this.each(typeof value === "function" | |
| ? function() { var v = value.apply(this, arguments); this.textContent = v == null ? "" : v; } : value == null | |
| ? function() { this.textContent = ""; } | |
| : function() { this.textContent = value; }); | |
| }; | |
| d3_selectionPrototype.html = function(value) { | |
| return arguments.length < 1 | |
| ? this.node().innerHTML : this.each(typeof value === "function" | |
| ? function() { var v = value.apply(this, arguments); this.innerHTML = v == null ? "" : v; } : value == null | |
| ? function() { this.innerHTML = ""; } | |
| : function() { this.innerHTML = value; }); | |
| }; | |
| // TODO append(node)? | |
| // TODO append(function)? | |
| d3_selectionPrototype.append = function(name) { | |
| name = d3.ns.qualify(name); | |
| function append() { | |
| return this.appendChild(document.createElementNS(this.namespaceURI, name)); | |
| } | |
| function appendNS() { | |
| return this.appendChild(document.createElementNS(name.space, name.local)); | |
| } | |
| return this.select(name.local ? appendNS : append); | |
| }; | |
| // TODO insert(node, function)? | |
| // TODO insert(function, string)? | |
| // TODO insert(function, function)? | |
| d3_selectionPrototype.insert = function(name, before) { | |
| name = d3.ns.qualify(name); | |
| function insert() { | |
| return this.insertBefore( | |
| document.createElementNS(this.namespaceURI, name), | |
| d3_select(before, this)); | |
| } | |
| function insertNS() { | |
| return this.insertBefore( | |
| document.createElementNS(name.space, name.local), | |
| d3_select(before, this)); | |
| } | |
| return this.select(name.local ? insertNS : insert); | |
| }; | |
| // TODO remove(selector)? | |
| // TODO remove(node)? | |
| // TODO remove(function)? | |
| d3_selectionPrototype.remove = function() { | |
| return this.each(function() { | |
| var parent = this.parentNode; | |
| if (parent) parent.removeChild(this); | |
| }); | |
| }; | |
| d3_selectionPrototype.data = function(value, key) { | |
| var i = -1, | |
| n = this.length, | |
| group, | |
| node; | |
| // If no value is specified, return the first value. | |
| if (!arguments.length) { | |
| value = new Array(n = (group = this[0]).length); | |
| while (++i < n) { | |
| if (node = group[i]) { | |
| value[i] = node.__data__; | |
| } | |
| } | |
| return value; | |
| } | |
| function bind(group, groupData) { | |
| var i, | |
| n = group.length, | |
| m = groupData.length, | |
| n0 = Math.min(n, m), | |
| n1 = Math.max(n, m), | |
| updateNodes = [], | |
| enterNodes = [], | |
| exitNodes = [], | |
| node, | |
| nodeData; | |
| if (key) { | |
| var nodeByKeyValue = new d3_Map, | |
| keyValues = [], | |
| keyValue, | |
| j = groupData.length; | |
| for (i = -1; ++i < n;) { | |
| keyValue = key.call(node = group[i], node.__data__, i); | |
| if (nodeByKeyValue.has(keyValue)) { | |
| exitNodes[j++] = node; // duplicate key | |
| } else { | |
| nodeByKeyValue.set(keyValue, node); | |
| } | |
| keyValues.push(keyValue); | |
| } | |
| for (i = -1; ++i < m;) { | |
| keyValue = key.call(groupData, nodeData = groupData[i], i) | |
| if (nodeByKeyValue.has(keyValue)) { | |
| updateNodes[i] = node = nodeByKeyValue.get(keyValue); | |
| node.__data__ = nodeData; | |
| enterNodes[i] = exitNodes[i] = null; | |
| } else { | |
| enterNodes[i] = d3_selection_dataNode(nodeData); | |
| updateNodes[i] = exitNodes[i] = null; | |
| } | |
| nodeByKeyValue.remove(keyValue); | |
| } | |
| for (i = -1; ++i < n;) { | |
| if (nodeByKeyValue.has(keyValues[i])) { | |
| exitNodes[i] = group[i]; | |
| } | |
| } | |
| } else { | |
| for (i = -1; ++i < n0;) { | |
| node = group[i]; | |
| nodeData = groupData[i]; | |
| if (node) { | |
| node.__data__ = nodeData; | |
| updateNodes[i] = node; | |
| enterNodes[i] = exitNodes[i] = null; | |
| } else { | |
| enterNodes[i] = d3_selection_dataNode(nodeData); | |
| updateNodes[i] = exitNodes[i] = null; | |
| } | |
| } | |
| for (; i < m; ++i) { | |
| enterNodes[i] = d3_selection_dataNode(groupData[i]); | |
| updateNodes[i] = exitNodes[i] = null; | |
| } | |
| for (; i < n1; ++i) { | |
| exitNodes[i] = group[i]; | |
| enterNodes[i] = updateNodes[i] = null; | |
| } | |
| } | |
| enterNodes.update | |
| = updateNodes; | |
| enterNodes.parentNode | |
| = updateNodes.parentNode | |
| = exitNodes.parentNode | |
| = group.parentNode; | |
| enter.push(enterNodes); | |
| update.push(updateNodes); | |
| exit.push(exitNodes); | |
| } | |
| var enter = d3_selection_enter([]), | |
| update = d3_selection([]), | |
| exit = d3_selection([]); | |
| if (typeof value === "function") { | |
| while (++i < n) { | |
| bind(group = this[i], value.call(group, group.parentNode.__data__, i)); | |
| } | |
| } else { | |
| while (++i < n) { | |
| bind(group = this[i], value); | |
| } | |
| } | |
| update.enter = function() { return enter; }; | |
| update.exit = function() { return exit; }; | |
| return update; | |
| }; | |
| function d3_selection_dataNode(data) { | |
| return {__data__: data}; | |
| } | |
| d3_selectionPrototype.datum = | |
| d3_selectionPrototype.map = function(value) { | |
| return arguments.length < 1 | |
| ? this.property("__data__") | |
| : this.property("__data__", value); | |
| }; | |
| d3_selectionPrototype.filter = function(filter) { | |
| var subgroups = [], | |
| subgroup, | |
| group, | |
| node; | |
| if (typeof filter !== "function") filter = d3_selection_filter(filter); | |
| for (var j = 0, m = this.length; j < m; j++) { | |
| subgroups.push(subgroup = []); | |
| subgroup.parentNode = (group = this[j]).parentNode; | |
| for (var i = 0, n = group.length; i < n; i++) { | |
| if ((node = group[i]) && filter.call(node, node.__data__, i)) { | |
| subgroup.push(node); | |
| } | |
| } | |
| } | |
| return d3_selection(subgroups); | |
| }; | |
| function d3_selection_filter(selector) { | |
| return function() { | |
| return d3_selectMatches(this, selector); | |
| }; | |
| } | |
| d3_selectionPrototype.order = function() { | |
| for (var j = -1, m = this.length; ++j < m;) { | |
| for (var group = this[j], i = group.length - 1, next = group[i], node; --i >= 0;) { | |
| if (node = group[i]) { | |
| if (next && next !== node.nextSibling) next.parentNode.insertBefore(node, next); | |
| next = node; | |
| } | |
| } | |
| } | |
| return this; | |
| }; | |
| d3_selectionPrototype.sort = function(comparator) { | |
| comparator = d3_selection_sortComparator.apply(this, arguments); | |
| for (var j = -1, m = this.length; ++j < m;) this[j].sort(comparator); | |
| return this.order(); | |
| }; | |
| function d3_selection_sortComparator(comparator) { | |
| if (!arguments.length) comparator = d3.ascending; | |
| return function(a, b) { | |
| return comparator(a && a.__data__, b && b.__data__); | |
| }; | |
| } | |
| d3_selectionPrototype.on = function(type, listener, capture) { | |
| var n = arguments.length; | |
| if (n < 3) { | |
| // For on(object) or on(object, boolean), the object specifies the event | |
| // types and listeners to add or remove. The optional boolean specifies | |
| // whether the listener captures events. | |
| if (typeof type !== "string") { | |
| if (n < 2) listener = false; | |
| for (capture in type) this.each(d3_selection_on(capture, type[capture], listener)); | |
| return this; | |
| } | |
| // For on(string), return the listener for the first node. | |
| if (n < 2) return (n = this.node()["__on" + type]) && n._; | |
| // For on(string, function), use the default capture. | |
| capture = false; | |
| } | |
| // Otherwise, a type, listener and capture are specified, and handled as below. | |
| return this.each(d3_selection_on(type, listener, capture)); | |
| }; | |
| function d3_selection_on(type, listener, capture) { | |
| var name = "__on" + type, i = type.indexOf("."); | |
| if (i > 0) type = type.substring(0, i); | |
| function onRemove() { | |
| var wrapper = this[name]; | |
| if (wrapper) { | |
| this.removeEventListener(type, wrapper, wrapper.$); | |
| delete this[name]; | |
| } | |
| } | |
| function onAdd() { | |
| var node = this, | |
| args = arguments; | |
| onRemove.call(this); | |
| this.addEventListener(type, this[name] = wrapper, wrapper.$ = capture); | |
| wrapper._ = listener; | |
| function wrapper(e) { | |
| var o = d3.event; // Events can be reentrant (e.g., focus). | |
| d3.event = e; | |
| args[0] = node.__data__; | |
| try { | |
| listener.apply(node, args); | |
| } finally { | |
| d3.event = o; | |
| } | |
| } | |
| } | |
| return listener ? onAdd : onRemove; | |
| } | |
| d3_selectionPrototype.each = function(callback) { | |
| return d3_selection_each(this, function(node, i, j) { | |
| callback.call(node, node.__data__, i, j); | |
| }); | |
| }; | |
| function d3_selection_each(groups, callback) { | |
| for (var j = 0, m = groups.length; j < m; j++) { | |
| for (var group = groups[j], i = 0, n = group.length, node; i < n; i++) { | |
| if (node = group[i]) callback(node, i, j); | |
| } | |
| } | |
| return groups; | |
| } | |
| // | |
| // Note: assigning to the arguments array simultaneously changes the value of | |
| // the corresponding argument! | |
| // | |
| // TODO The `this` argument probably shouldn't be the first argument to the | |
| // callback, anyway, since it's redundant. However, that will require a major | |
| // version bump due to backwards compatibility, so I'm not changing it right | |
| // away. | |
| // | |
| d3_selectionPrototype.call = function(callback) { | |
| callback.apply(this, (arguments[0] = this, arguments)); | |
| return this; | |
| }; | |
| d3_selectionPrototype.empty = function() { | |
| return !this.node(); | |
| }; | |
| d3_selectionPrototype.node = function(callback) { | |
| for (var j = 0, m = this.length; j < m; j++) { | |
| for (var group = this[j], i = 0, n = group.length; i < n; i++) { | |
| var node = group[i]; | |
| if (node) return node; | |
| } | |
| } | |
| return null; | |
| }; | |
| d3_selectionPrototype.transition = function() { | |
| var subgroups = [], | |
| subgroup, | |
| node; | |
| for (var j = -1, m = this.length; ++j < m;) { | |
| subgroups.push(subgroup = []); | |
| for (var group = this[j], i = -1, n = group.length; ++i < n;) { | |
| subgroup.push((node = group[i]) ? {node: node, delay: d3_transitionDelay, duration: d3_transitionDuration} : null); | |
| } | |
| } | |
| return d3_transition(subgroups, d3_transitionId || ++d3_transitionNextId, Date.now()); | |
| }; | |
| var d3_selectionRoot = d3_selection([[document]]); | |
| d3_selectionRoot[0].parentNode = d3_selectRoot; | |
| // TODO fast singleton implementation! | |
| // TODO select(function) | |
| d3.select = function(selector) { | |
| return typeof selector === "string" | |
| ? d3_selectionRoot.select(selector) | |
| : d3_selection([[selector]]); // assume node | |
| }; | |
| // TODO selectAll(function) | |
| d3.selectAll = function(selector) { | |
| return typeof selector === "string" | |
| ? d3_selectionRoot.selectAll(selector) | |
| : d3_selection([d3_array(selector)]); // assume node[] | |
| }; | |
| function d3_selection_enter(selection) { | |
| d3_arraySubclass(selection, d3_selection_enterPrototype); | |
| return selection; | |
| } | |
| var d3_selection_enterPrototype = []; | |
| d3.selection.enter = d3_selection_enter; | |
| d3.selection.enter.prototype = d3_selection_enterPrototype; | |
| d3_selection_enterPrototype.append = d3_selectionPrototype.append; | |
| d3_selection_enterPrototype.insert = d3_selectionPrototype.insert; | |
| d3_selection_enterPrototype.empty = d3_selectionPrototype.empty; | |
| d3_selection_enterPrototype.node = d3_selectionPrototype.node; | |
| d3_selection_enterPrototype.select = function(selector) { | |
| var subgroups = [], | |
| subgroup, | |
| subnode, | |
| upgroup, | |
| group, | |
| node; | |
| for (var j = -1, m = this.length; ++j < m;) { | |
| upgroup = (group = this[j]).update; | |
| subgroups.push(subgroup = []); | |
| subgroup.parentNode = group.parentNode; | |
| for (var i = -1, n = group.length; ++i < n;) { | |
| if (node = group[i]) { | |
| subgroup.push(upgroup[i] = subnode = selector.call(group.parentNode, node.__data__, i)); | |
| subnode.__data__ = node.__data__; | |
| } else { | |
| subgroup.push(null); | |
| } | |
| } | |
| } | |
| return d3_selection(subgroups); | |
| }; | |
| function d3_transition(groups, id, time) { | |
| d3_arraySubclass(groups, d3_transitionPrototype); | |
| var tweens = new d3_Map, | |
| event = d3.dispatch("start", "end"), | |
| ease = d3_transitionEase; | |
| groups.id = id; | |
| groups.time = time; | |
| groups.tween = function(name, tween) { | |
| if (arguments.length < 2) return tweens.get(name); | |
| if (tween == null) tweens.remove(name); | |
| else tweens.set(name, tween); | |
| return groups; | |
| }; | |
| groups.ease = function(value) { | |
| if (!arguments.length) return ease; | |
| ease = typeof value === "function" ? value : d3.ease.apply(d3, arguments); | |
| return groups; | |
| }; | |
| groups.each = function(type, listener) { | |
| if (arguments.length < 2) return d3_transition_each.call(groups, type); | |
| event.on(type, listener); | |
| return groups; | |
| }; | |
| d3.timer(function(elapsed) { | |
| return d3_selection_each(groups, function(node, i, j) { | |
| var tweened = [], | |
| delay = node.delay, | |
| duration = node.duration, | |
| lock = (node = node.node).__transition__ || (node.__transition__ = {active: 0, count: 0}), | |
| d = node.__data__; | |
| ++lock.count; | |
| delay <= elapsed ? start(elapsed) : d3.timer(start, delay, time); | |
| function start(elapsed) { | |
| if (lock.active > id) return stop(); | |
| lock.active = id; | |
| tweens.forEach(function(key, value) { | |
| if (value = value.call(node, d, i)) { | |
| tweened.push(value); | |
| } | |
| }); | |
| event.start.call(node, d, i); | |
| if (!tick(elapsed)) d3.timer(tick, 0, time); | |
| return 1; | |
| } | |
| function tick(elapsed) { | |
| if (lock.active !== id) return stop(); | |
| var t = (elapsed - delay) / duration, | |
| e = ease(t), | |
| n = tweened.length; | |
| while (n > 0) { | |
| tweened[--n].call(node, e); | |
| } | |
| if (t >= 1) { | |
| stop(); | |
| d3_transitionId = id; | |
| event.end.call(node, d, i); | |
| d3_transitionId = 0; | |
| return 1; | |
| } | |
| } | |
| function stop() { | |
| if (!--lock.count) delete node.__transition__; | |
| return 1; | |
| } | |
| }); | |
| }, 0, time); | |
| return groups; | |
| } | |
| var d3_transitionPrototype = [], | |
| d3_transitionNextId = 0, | |
| d3_transitionId = 0, | |
| d3_transitionDefaultDelay = 0, | |
| d3_transitionDefaultDuration = 250, | |
| d3_transitionDefaultEase = d3.ease("cubic-in-out"), | |
| d3_transitionDelay = d3_transitionDefaultDelay, | |
| d3_transitionDuration = d3_transitionDefaultDuration, | |
| d3_transitionEase = d3_transitionDefaultEase; | |
| d3_transitionPrototype.call = d3_selectionPrototype.call; | |
| d3.transition = function(selection) { | |
| return arguments.length | |
| ? (d3_transitionId ? selection.transition() : selection) | |
| : d3_selectionRoot.transition(); | |
| }; | |
| d3.transition.prototype = d3_transitionPrototype; | |
| d3_transitionPrototype.select = function(selector) { | |
| var subgroups = [], | |
| subgroup, | |
| subnode, | |
| node; | |
| if (typeof selector !== "function") selector = d3_selection_selector(selector); | |
| for (var j = -1, m = this.length; ++j < m;) { | |
| subgroups.push(subgroup = []); | |
| for (var group = this[j], i = -1, n = group.length; ++i < n;) { | |
| if ((node = group[i]) && (subnode = selector.call(node.node, node.node.__data__, i))) { | |
| if ("__data__" in node.node) subnode.__data__ = node.node.__data__; | |
| subgroup.push({node: subnode, delay: node.delay, duration: node.duration}); | |
| } else { | |
| subgroup.push(null); | |
| } | |
| } | |
| } | |
| return d3_transition(subgroups, this.id, this.time).ease(this.ease()); | |
| }; | |
| d3_transitionPrototype.selectAll = function(selector) { | |
| var subgroups = [], | |
| subgroup, | |
| subnodes, | |
| node; | |
| if (typeof selector !== "function") selector = d3_selection_selectorAll(selector); | |
| for (var j = -1, m = this.length; ++j < m;) { | |
| for (var group = this[j], i = -1, n = group.length; ++i < n;) { | |
| if (node = group[i]) { | |
| subnodes = selector.call(node.node, node.node.__data__, i); | |
| subgroups.push(subgroup = []); | |
| for (var k = -1, o = subnodes.length; ++k < o;) { | |
| subgroup.push({node: subnodes[k], delay: node.delay, duration: node.duration}); | |
| } | |
| } | |
| } | |
| } | |
| return d3_transition(subgroups, this.id, this.time).ease(this.ease()); | |
| }; | |
| d3_transitionPrototype.filter = function(filter) { | |
| var subgroups = [], | |
| subgroup, | |
| group, | |
| node; | |
| if (typeof filter !== "function") filter = d3_selection_filter(filter); | |
| for (var j = 0, m = this.length; j < m; j++) { | |
| subgroups.push(subgroup = []); | |
| for (var group = this[j], i = 0, n = group.length; i < n; i++) { | |
| if ((node = group[i]) && filter.call(node.node, node.node.__data__, i)) { | |
| subgroup.push(node); | |
| } | |
| } | |
| } | |
| return d3_transition(subgroups, this.id, this.time).ease(this.ease()); | |
| }; | |
| d3_transitionPrototype.attr = function(name, value) { | |
| if (arguments.length < 2) { | |
| // For attr(object), the object specifies the names and values of the | |
| // attributes to transition. The values may be functions that are | |
| // evaluated for each element. | |
| for (value in name) this.attrTween(value, d3_tweenByName(name[value], value)); | |
| return this; | |
| } | |
| return this.attrTween(name, d3_tweenByName(value, name)); | |
| }; | |
| d3_transitionPrototype.attrTween = function(nameNS, tween) { | |
| var name = d3.ns.qualify(nameNS); | |
| function attrTween(d, i) { | |
| var f = tween.call(this, d, i, this.getAttribute(name)); | |
| return f === d3_tweenRemove | |
| ? (this.removeAttribute(name), null) | |
| : f && function(t) { this.setAttribute(name, f(t)); }; | |
| } | |
| function attrTweenNS(d, i) { | |
| var f = tween.call(this, d, i, this.getAttributeNS(name.space, name.local)); | |
| return f === d3_tweenRemove | |
| ? (this.removeAttributeNS(name.space, name.local), null) | |
| : f && function(t) { this.setAttributeNS(name.space, name.local, f(t)); }; | |
| } | |
| return this.tween("attr." + nameNS, name.local ? attrTweenNS : attrTween); | |
| }; | |
| d3_transitionPrototype.style = function(name, value, priority) { | |
| var n = arguments.length; | |
| if (n < 3) { | |
| // For style(object) or style(object, string), the object specifies the | |
| // names and values of the attributes to set or remove. The values may be | |
| // functions that are evaluated for each element. The optional string | |
| // specifies the priority. | |
| if (typeof name !== "string") { | |
| if (n < 2) value = ""; | |
| for (priority in name) this.styleTween(priority, d3_tweenByName(name[priority], priority), value); | |
| return this; | |
| } | |
| // For style(string, string) or style(string, function), use the default | |
| // priority. The priority is ignored for style(string, null). | |
| priority = ""; | |
| } | |
| // Otherwise, a name, value and priority are specified, and handled as below. | |
| return this.styleTween(name, d3_tweenByName(value, name), priority); | |
| }; | |
| d3_transitionPrototype.styleTween = function(name, tween, priority) { | |
| if (arguments.length < 3) priority = ""; | |
| return this.tween("style." + name, function(d, i) { | |
| var f = tween.call(this, d, i, window.getComputedStyle(this, null).getPropertyValue(name)); | |
| return f === d3_tweenRemove | |
| ? (this.style.removeProperty(name), null) | |
| : f && function(t) { this.style.setProperty(name, f(t), priority); }; | |
| }); | |
| }; | |
| d3_transitionPrototype.text = function(value) { | |
| return this.tween("text", function(d, i) { | |
| this.textContent = typeof value === "function" | |
| ? value.call(this, d, i) | |
| : value; | |
| }); | |
| }; | |
| d3_transitionPrototype.remove = function() { | |
| return this.each("end.transition", function() { | |
| var p; | |
| if (!this.__transition__ && (p = this.parentNode)) p.removeChild(this); | |
| }); | |
| }; | |
| d3_transitionPrototype.delay = function(value) { | |
| return d3_selection_each(this, typeof value === "function" | |
| ? function(node, i, j) { node.delay = value.call(node = node.node, node.__data__, i, j) | 0; } | |
| : (value = value | 0, function(node) { node.delay = value; })); | |
| }; | |
| d3_transitionPrototype.duration = function(value) { | |
| return d3_selection_each(this, typeof value === "function" | |
| ? function(node, i, j) { node.duration = Math.max(1, value.call(node = node.node, node.__data__, i, j) | 0); } | |
| : (value = Math.max(1, value | 0), function(node) { node.duration = value; })); | |
| }; | |
| function d3_transition_each(callback) { | |
| var id = d3_transitionId, | |
| ease = d3_transitionEase, | |
| delay = d3_transitionDelay, | |
| duration = d3_transitionDuration; | |
| d3_transitionId = this.id; | |
| d3_transitionEase = this.ease(); | |
| d3_selection_each(this, function(node, i, j) { | |
| d3_transitionDelay = node.delay; | |
| d3_transitionDuration = node.duration; | |
| callback.call(node = node.node, node.__data__, i, j); | |
| }); | |
| d3_transitionId = id; | |
| d3_transitionEase = ease; | |
| d3_transitionDelay = delay; | |
| d3_transitionDuration = duration; | |
| return this; | |
| } | |
| d3_transitionPrototype.transition = function() { | |
| return this.select(d3_this); | |
| }; | |
| d3.tween = function(b, interpolate) { | |
| function tweenFunction(d, i, a) { | |
| var v = b.call(this, d, i); | |
| return v == null | |
| ? a != "" && d3_tweenRemove | |
| : a != v && interpolate(a, v); | |
| } | |
| function tweenString(d, i, a) { | |
| return a != b && interpolate(a, b); | |
| } | |
| return typeof b === "function" ? tweenFunction | |
| : b == null ? d3_tweenNull | |
| : (b += "", tweenString); | |
| }; | |
| var d3_tweenRemove = {}; | |
| function d3_tweenNull(d, i, a) { | |
| return a != "" && d3_tweenRemove; | |
| } | |
| function d3_tweenByName(b, name) { | |
| return d3.tween(b, d3_interpolateByName(name)); | |
| } | |
| var d3_timer_queue = null, | |
| d3_timer_interval, // is an interval (or frame) active? | |
| d3_timer_timeout; // is a timeout active? | |
| // The timer will continue to fire until callback returns true. | |
| d3.timer = function(callback, delay, then) { | |
| var found = false, | |
| t0, | |
| t1 = d3_timer_queue; | |
| if (arguments.length < 3) { | |
| if (arguments.length < 2) delay = 0; | |
| else if (!isFinite(delay)) return; | |
| then = Date.now(); | |
| } | |
| // See if the callback's already in the queue. | |
| while (t1) { | |
| if (t1.callback === callback) { | |
| t1.then = then; | |
| t1.delay = delay; | |
| found = true; | |
| break; | |
| } | |
| t0 = t1; | |
| t1 = t1.next; | |
| } | |
| // Otherwise, add the callback to the queue. | |
| if (!found) d3_timer_queue = { | |
| callback: callback, | |
| then: then, | |
| delay: delay, | |
| next: d3_timer_queue | |
| }; | |
| // Start animatin'! | |
| if (!d3_timer_interval) { | |
| d3_timer_timeout = clearTimeout(d3_timer_timeout); | |
| d3_timer_interval = 1; | |
| d3_timer_frame(d3_timer_step); | |
| } | |
| } | |
| function d3_timer_step() { | |
| var elapsed, | |
| now = Date.now(), | |
| t1 = d3_timer_queue; | |
| while (t1) { | |
| elapsed = now - t1.then; | |
| if (elapsed >= t1.delay) t1.flush = t1.callback(elapsed); | |
| t1 = t1.next; | |
| } | |
| var delay = d3_timer_flush() - now; | |
| if (delay > 24) { | |
| if (isFinite(delay)) { | |
| clearTimeout(d3_timer_timeout); | |
| d3_timer_timeout = setTimeout(d3_timer_step, delay); | |
| } | |
| d3_timer_interval = 0; | |
| } else { | |
| d3_timer_interval = 1; | |
| d3_timer_frame(d3_timer_step); | |
| } | |
| } | |
| d3.timer.flush = function() { | |
| var elapsed, | |
| now = Date.now(), | |
| t1 = d3_timer_queue; | |
| while (t1) { | |
| elapsed = now - t1.then; | |
| if (!t1.delay) t1.flush = t1.callback(elapsed); | |
| t1 = t1.next; | |
| } | |
| d3_timer_flush(); | |
| }; | |
| // Flush after callbacks, to avoid concurrent queue modification. | |
| function d3_timer_flush() { | |
| var t0 = null, | |
| t1 = d3_timer_queue, | |
| then = Infinity; | |
| while (t1) { | |
| if (t1.flush) { | |
| t1 = t0 ? t0.next = t1.next : d3_timer_queue = t1.next; | |
| } else { | |
| then = Math.min(then, t1.then + t1.delay); | |
| t1 = (t0 = t1).next; | |
| } | |
| } | |
| return then; | |
| } | |
| var d3_timer_frame = window.requestAnimationFrame | |
| || window.webkitRequestAnimationFrame | |
| || window.mozRequestAnimationFrame | |
| || window.oRequestAnimationFrame | |
| || window.msRequestAnimationFrame | |
| || function(callback) { setTimeout(callback, 17); }; | |
| d3.mouse = function(container) { | |
| return d3_mousePoint(container, d3_eventSource()); | |
| }; | |
| var d3_mouse_getScreenCTM; | |
| if (/WebKit/.test(navigator.userAgent)) { | |
| var d3_mouse_bug44083 = -1; // https://bugs.webkit.org/show_bug.cgi?id=44083 | |
| var d3_mouse_zoom_bug = -1; // ToDo: file bug report? | |
| d3_mouse_getScreenCTM = function(container, e) { | |
| var ctm, | |
| test_bug44083 = (d3_mouse_bug44083 < 0) && (window.pageXOffset || window.pageYOffset), | |
| // Assuming zoom does the same in X and Y so only testing X | |
| test_zoom_bug = (d3_mouse_zoom_bug < 0) && e.clientX && (e.screenX - window.screenLeft !== e.clientX); | |
| if (test_bug44083 || test_zoom_bug) { | |
| var body = document.body, | |
| bodyPos = body.style.getPropertyValue('position'), | |
| html = body.parentNode, | |
| htmlPos = html.style.getPropertyValue('position'), | |
| svg = d3.select(body).append("svg").style("position", "absolute"); | |
| body.style.setProperty('position', 'static'); | |
| html.style.setProperty('position', 'static'); | |
| if (test_bug44083) { | |
| svg.style("top", 0).style("left", 0); | |
| ctm = svg[0][0].getScreenCTM(); | |
| d3_mouse_bug44083 = !(ctm.f || ctm.e); | |
| } | |
| if (test_zoom_bug) { | |
| svg.style("left", "100px"); | |
| ctm = svg[0][0].getScreenCTM(); | |
| d3_mouse_zoom_bug = (ctm.e !== 100); | |
| } | |
| svg.remove(); | |
| bodyPos ? body.style.setProperty('position', bodyPos) : body.style.removeProperty('position'); | |
| htmlPos ? html.style.setProperty('position', htmlPos) : html.style.removeProperty('position'); | |
| } | |
| ctm = container.getScreenCTM(); | |
| if (d3_mouse_bug44083) { | |
| ctm = ctm.translate(window.pageXOffset, window.pageYOffset); | |
| } | |
| if (d3_mouse_zoom_bug) { | |
| // zoom factor [z = (e.screenX - window.screenLeft) / e.clientX], ctm should be 1/z of it's position | |
| // the assumption is that zoomX == zoomY if someone can find a browser where this is not true then... | |
| var s = e.clientX / (e.screenX - window.screenLeft) - 1; | |
| ctm = ctm.translate(ctm.e * s, ctm.f * s); | |
| } | |
| return ctm; | |
| } | |
| } else { | |
| d3_mouse_getScreenCTM = function(container) { | |
| return container.getScreenCTM(); | |
| } | |
| } | |
| function d3_mousePoint(container, e) { | |
| var svg = container.ownerSVGElement || container; | |
| if (svg.createSVGPoint) { | |
| var point = svg.createSVGPoint(); | |
| point.x = e.clientX; | |
| point.y = e.clientY; | |
| point = point.matrixTransform(d3_mouse_getScreenCTM(container, e).inverse()); | |
| return [point.x, point.y]; | |
| } | |
| var rect = container.getBoundingClientRect(); | |
| return [e.clientX - rect.left - container.clientLeft, e.clientY - rect.top - container.clientTop]; | |
| }; | |
| d3.touches = function(container, touches) { | |
| if (arguments.length < 2) touches = d3_eventSource().touches; | |
| return touches ? d3_array(touches).map(function(touch) { | |
| var point = d3_mousePoint(container, touch); | |
| point.identifier = touch.identifier; | |
| return point; | |
| }) : []; | |
| }; | |
| function d3_noop() {} | |
| d3.scale = {}; | |
| function d3_scaleExtent(domain) { | |
| var start = domain[0], stop = domain[domain.length - 1]; | |
| return start < stop ? [start, stop] : [stop, start]; | |
| } | |
| function d3_scaleRange(scale) { | |
| return scale.rangeExtent ? scale.rangeExtent() : d3_scaleExtent(scale.range()); | |
| } | |
| function d3_scale_nice(domain, nice) { | |
| var i0 = 0, | |
| i1 = domain.length - 1, | |
| x0 = domain[i0], | |
| x1 = domain[i1], | |
| dx; | |
| if (x1 < x0) { | |
| dx = i0, i0 = i1, i1 = dx; | |
| dx = x0, x0 = x1, x1 = dx; | |
| } | |
| if (nice = nice(x1 - x0)) { | |
| domain[i0] = nice.floor(x0); | |
| domain[i1] = nice.ceil(x1); | |
| } | |
| return domain; | |
| } | |
| function d3_scale_niceDefault() { | |
| return Math; | |
| } | |
| d3.scale.linear = function() { | |
| return d3_scale_linear([0, 1], [0, 1], d3.interpolate, false); | |
| }; | |
| function d3_scale_linear(domain, range, interpolate, clamp) { | |
| var output, | |
| input; | |
| function rescale() { | |
| var linear = Math.min(domain.length, range.length) > 2 ? d3_scale_polylinear : d3_scale_bilinear, | |
| uninterpolate = clamp ? d3_uninterpolateClamp : d3_uninterpolateNumber; | |
| output = linear(domain, range, uninterpolate, interpolate); | |
| input = linear(range, domain, uninterpolate, d3.interpolate); | |
| return scale; | |
| } | |
| function scale(x) { | |
| return output(x); | |
| } | |
| // Note: requires range is coercible to number! | |
| scale.invert = function(y) { | |
| return input(y); | |
| }; | |
| scale.domain = function(x) { | |
| if (!arguments.length) return domain; | |
| domain = x.map(Number); | |
| return rescale(); | |
| }; | |
| scale.range = function(x) { | |
| if (!arguments.length) return range; | |
| range = x; | |
| return rescale(); | |
| }; | |
| scale.rangeRound = function(x) { | |
| return scale.range(x).interpolate(d3.interpolateRound); | |
| }; | |
| scale.clamp = function(x) { | |
| if (!arguments.length) return clamp; | |
| clamp = x; | |
| return rescale(); | |
| }; | |
| scale.interpolate = function(x) { | |
| if (!arguments.length) return interpolate; | |
| interpolate = x; | |
| return rescale(); | |
| }; | |
| scale.ticks = function(m) { | |
| return d3_scale_linearTicks(domain, m); | |
| }; | |
| scale.tickFormat = function(m) { | |
| return d3_scale_linearTickFormat(domain, m); | |
| }; | |
| scale.nice = function() { | |
| d3_scale_nice(domain, d3_scale_linearNice); | |
| return rescale(); | |
| }; | |
| scale.copy = function() { | |
| return d3_scale_linear(domain, range, interpolate, clamp); | |
| }; | |
| return rescale(); | |
| } | |
| 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_linearTickRange(domain, m) { | |
| var extent = d3_scaleExtent(domain), | |
| span = extent[1] - extent[0], | |
| step = Math.pow(10, Math.floor(Math.log(span / m) / Math.LN10)), | |
| err = m / span * step; | |
| // Filter ticks to get closer to the desired count. | |
| if (err <= .15) step *= 10; | |
| else if (err <= .35) step *= 5; | |
| else if (err <= .75) step *= 2; | |
| // Round start and stop values to step interval. | |
| extent[0] = Math.ceil(extent[0] / step) * step; | |
| extent[1] = Math.floor(extent[1] / step) * step + step * .5; // inclusive | |
| extent[2] = step; | |
| return extent; | |
| } | |
| function d3_scale_linearTicks(domain, m) { | |
| return d3.range.apply(d3, d3_scale_linearTickRange(domain, m)); | |
| } | |
| function d3_scale_linearTickFormat(domain, m) { | |
| return d3.format(",." + Math.max(0, -Math.floor(Math.log(d3_scale_linearTickRange(domain, m)[2]) / Math.LN10 + .01)) + "f"); | |
| } | |
| function d3_scale_bilinear(domain, range, uninterpolate, interpolate) { | |
| var u = uninterpolate(domain[0], domain[1]), | |
| i = interpolate(range[0], range[1]); | |
| return function(x) { | |
| return i(u(x)); | |
| }; | |
| } | |
| function d3_scale_polylinear(domain, range, uninterpolate, interpolate) { | |
| var u = [], | |
| i = [], | |
| j = 0, | |
| k = Math.min(domain.length, range.length) - 1; | |
| // Handle descending domains. | |
| if (domain[k] < domain[0]) { | |
| domain = domain.slice().reverse(); | |
| range = range.slice().reverse(); | |
| } | |
| while (++j <= k) { | |
| u.push(uninterpolate(domain[j - 1], domain[j])); | |
| i.push(interpolate(range[j - 1], range[j])); | |
| } | |
| return function(x) { | |
| var j = d3.bisect(domain, x, 1, k) - 1; | |
| return i[j](u[j](x)); | |
| }; | |
| } | |
| d3.scale.log = function() { | |
| return d3_scale_log(d3.scale.linear(), d3_scale_logp); | |
| }; | |
| function d3_scale_log(linear, log) { | |
| var pow = log.pow; | |
| function scale(x) { | |
| return linear(log(x)); | |
| } | |
| scale.invert = function(x) { | |
| return pow(linear.invert(x)); | |
| }; | |
| scale.domain = function(x) { | |
| if (!arguments.length) return linear.domain().map(pow); | |
| log = x[0] < 0 ? d3_scale_logn : d3_scale_logp; | |
| pow = log.pow; | |
| linear.domain(x.map(log)); | |
| return scale; | |
| }; | |
| scale.nice = function() { | |
| linear.domain(d3_scale_nice(linear.domain(), d3_scale_niceDefault)); | |
| return scale; | |
| }; | |
| scale.ticks = function() { | |
| var extent = d3_scaleExtent(linear.domain()), | |
| ticks = []; | |
| if (extent.every(isFinite)) { | |
| var i = Math.floor(extent[0]), | |
| j = Math.ceil(extent[1]), | |
| u = pow(extent[0]), | |
| v = pow(extent[1]); | |
| if (log === d3_scale_logn) { | |
| ticks.push(pow(i)); | |
| for (; i++ < j;) for (var k = 9; k > 0; k--) ticks.push(pow(i) * k); | |
| } else { | |
| for (; i < j; i++) for (var k = 1; k < 10; k++) ticks.push(pow(i) * k); | |
| ticks.push(pow(i)); | |
| } | |
| for (i = 0; ticks[i] < u; i++) {} // strip small values | |
| for (j = ticks.length; ticks[j - 1] > v; j--) {} // strip big values | |
| ticks = ticks.slice(i, j); | |
| } | |
| return ticks; | |
| }; | |
| scale.tickFormat = function(n, format) { | |
| if (arguments.length < 2) format = d3_scale_logFormat; | |
| if (arguments.length < 1) return format; | |
| var k = Math.max(.1, n / scale.ticks().length), | |
| f = log === d3_scale_logn ? (e = -1e-12, Math.floor) : (e = 1e-12, Math.ceil), | |
| e, | |
| h = (k >= 0.5); | |
| // Always try to print the .5 tick text whenever possible, f.e.: 1,2,3,5 is better than 1,2,3,4. | |
| // If you can do 1,2,3 you can also safely do 1,2,3,5. | |
| // If you can do 1,2-and-a-bit you can also safely do 1,2,5. | |
| // But 1,2 is better than 1,5 for very tight ticks in a log scale. | |
| if (k < 0.5) { | |
| if (k >= 0.4) { | |
| k -= 0.1; | |
| h = true; | |
| } else if (k > 0.23) { | |
| h = true; | |
| } | |
| } | |
| return function(d) { | |
| var r = d / pow(f(log(d) + e)); | |
| // round to two decimal places to uniquely pull out the half-way (.5) tick | |
| // (floating point 'equals' comparisons are dangerous, so we make sure Math.round produces an integer result) | |
| if (r <= k || (h && Math.round(200 * r) == 100)) | |
| return format(d); | |
| return ""; | |
| }; | |
| }; | |
| scale.copy = function() { | |
| return d3_scale_log(linear.copy(), log); | |
| }; | |
| return d3_scale_linearRebind(scale, linear); | |
| } | |
| var d3_scale_logFormat = d3.format(".0E"); | |
| function d3_scale_logp(x) { | |
| return Math.log(x < 0 ? 0 : x) / Math.LN10; | |
| } | |
| function d3_scale_logn(x) { | |
| return -Math.log(x > 0 ? 0 : -x) / Math.LN10; | |
| } | |
| d3_scale_logp.pow = function(x) { | |
| return Math.pow(10, x); | |
| }; | |
| d3_scale_logn.pow = function(x) { | |
| return -Math.pow(10, -x); | |
| }; | |
| d3.scale.pow = function() { | |
| return d3_scale_pow(d3.scale.linear(), 1); | |
| }; | |
| function d3_scale_pow(linear, exponent) { | |
| var powp = d3_scale_powPow(exponent), | |
| powb = d3_scale_powPow(1 / exponent); | |
| function scale(x) { | |
| return linear(powp(x)); | |
| } | |
| scale.invert = function(x) { | |
| return powb(linear.invert(x)); | |
| }; | |
| scale.domain = function(x) { | |
| if (!arguments.length) return linear.domain().map(powb); | |
| linear.domain(x.map(powp)); | |
| return scale; | |
| }; | |
| scale.ticks = function(m) { | |
| return d3_scale_linearTicks(scale.domain(), m); | |
| }; | |
| scale.tickFormat = function(m) { | |
| return d3_scale_linearTickFormat(scale.domain(), m); | |
| }; | |
| scale.nice = function() { | |
| return scale.domain(d3_scale_nice(scale.domain(), d3_scale_linearNice)); | |
| }; | |
| scale.exponent = function(x) { | |
| if (!arguments.length) return exponent; | |
| var domain = scale.domain(); | |
| powp = d3_scale_powPow(exponent = x); | |
| powb = d3_scale_powPow(1 / exponent); | |
| return scale.domain(domain); | |
| }; | |
| scale.copy = function() { | |
| return d3_scale_pow(linear.copy(), exponent); | |
| }; | |
| return d3_scale_linearRebind(scale, linear); | |
| } | |
| function d3_scale_powPow(e) { | |
| return function(x) { | |
| return x < 0 ? -Math.pow(-x, e) : Math.pow(x, e); | |
| }; | |
| } | |
| d3.scale.sqrt = function() { | |
| return d3.scale.pow().exponent(.5); | |
| }; | |
| d3.scale.ordinal = function() { | |
| return d3_scale_ordinal([], {t: "range", a: [[]]}); | |
| }; | |
| function d3_scale_ordinal(domain, ranger) { | |
| var index, | |
| range, | |
| rangeBand; | |
| function scale(x) { | |
| return range[((index.get(x) || index.set(x, domain.push(x))) - 1) % range.length]; | |
| } | |
| function steps(start, step) { | |
| return d3.range(domain.length).map(function(i) { return start + step * i; }); | |
| } | |
| scale.domain = function(x) { | |
| if (!arguments.length) return domain; | |
| domain = []; | |
| index = new d3_Map; | |
| var i = -1, n = x.length, xi; | |
| while (++i < n) if (!index.has(xi = x[i])) index.set(xi, domain.push(xi)); | |
| return scale[ranger.t].apply(scale, ranger.a); | |
| }; | |
| scale.range = function(x) { | |
| if (!arguments.length) return range; | |
| range = x; | |
| rangeBand = 0; | |
| ranger = {t: "range", a: arguments}; | |
| return scale; | |
| }; | |
| scale.rangePoints = function(x, padding) { | |
| if (arguments.length < 2) padding = 0; | |
| var start = x[0], | |
| stop = x[1], | |
| step = (stop - start) / (Math.max(1, domain.length - 1) + padding); | |
| range = steps(domain.length < 2 ? (start + stop) / 2 : start + step * padding / 2, step); | |
| rangeBand = 0; | |
| ranger = {t: "rangePoints", a: arguments}; | |
| return scale; | |
| }; | |
| scale.rangeBands = function(x, padding, outerPadding) { | |
| if (arguments.length < 2) padding = 0; | |
| if (arguments.length < 3) outerPadding = padding; | |
| var reverse = x[1] < x[0], | |
| start = x[reverse - 0], | |
| stop = x[1 - reverse], | |
| step = (stop - start) / (domain.length - padding + 2 * outerPadding); | |
| range = steps(start + step * outerPadding, step); | |
| if (reverse) range.reverse(); | |
| rangeBand = step * (1 - padding); | |
| ranger = {t: "rangeBands", a: arguments}; | |
| return scale; | |
| }; | |
| scale.rangeRoundBands = function(x, padding, outerPadding) { | |
| if (arguments.length < 2) padding = 0; | |
| if (arguments.length < 3) outerPadding = padding; | |
| var reverse = x[1] < x[0], | |
| start = x[reverse - 0], | |
| stop = x[1 - reverse], | |
| step = Math.floor((stop - start) / (domain.length - padding + 2 * outerPadding)), | |
| error = stop - start - (domain.length - padding) * step; | |
| range = steps(start + Math.round(error / 2), step); | |
| if (reverse) range.reverse(); | |
| rangeBand = Math.round(step * (1 - padding)); | |
| ranger = {t: "rangeRoundBands", a: arguments}; | |
| return scale; | |
| }; | |
| scale.rangeBand = function() { | |
| return rangeBand; | |
| }; | |
| scale.rangeExtent = function() { | |
| return d3_scaleExtent(ranger.a[0]); | |
| }; | |
| scale.copy = function() { | |
| return d3_scale_ordinal(domain, ranger); | |
| }; | |
| return scale.domain(domain); | |
| } | |
| /* | |
| * This product includes color specifications and designs developed by Cynthia | |
| * Brewer (http://colorbrewer.org/). See lib/colorbrewer for more information. | |
| */ | |
| d3.scale.category10 = function() { | |
| return d3.scale.ordinal().range(d3_category10); | |
| }; | |
| d3.scale.category20 = function() { | |
| return d3.scale.ordinal().range(d3_category20); | |
| }; | |
| d3.scale.category20b = function() { | |
| return d3.scale.ordinal().range(d3_category20b); | |
| }; | |
| d3.scale.category20c = function() { | |
| return d3.scale.ordinal().range(d3_category20c); | |
| }; | |
| var d3_category10 = [ | |
| "#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", | |
| "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf" | |
| ]; | |
| var d3_category20 = [ | |
| "#1f77b4", "#aec7e8", | |
| "#ff7f0e", "#ffbb78", | |
| "#2ca02c", "#98df8a", | |
| "#d62728", "#ff9896", | |
| "#9467bd", "#c5b0d5", | |
| "#8c564b", "#c49c94", | |
| "#e377c2", "#f7b6d2", | |
| "#7f7f7f", "#c7c7c7", | |
| "#bcbd22", "#dbdb8d", | |
| "#17becf", "#9edae5" | |
| ]; | |
| var d3_category20b = [ | |
| "#393b79", "#5254a3", "#6b6ecf", "#9c9ede", | |
| "#637939", "#8ca252", "#b5cf6b", "#cedb9c", | |
| "#8c6d31", "#bd9e39", "#e7ba52", "#e7cb94", | |
| "#843c39", "#ad494a", "#d6616b", "#e7969c", | |
| "#7b4173", "#a55194", "#ce6dbd", "#de9ed6" | |
| ]; | |
| var d3_category20c = [ | |
| "#3182bd", "#6baed6", "#9ecae1", "#c6dbef", | |
| "#e6550d", "#fd8d3c", "#fdae6b", "#fdd0a2", | |
| "#31a354", "#74c476", "#a1d99b", "#c7e9c0", | |
| "#756bb1", "#9e9ac8", "#bcbddc", "#dadaeb", | |
| "#636363", "#969696", "#bdbdbd", "#d9d9d9" | |
| ]; | |
| d3.scale.quantile = function() { | |
| return d3_scale_quantile([], []); | |
| }; | |
| function d3_scale_quantile(domain, range) { | |
| var thresholds; | |
| function rescale() { | |
| var k = 0, | |
| n = domain.length, | |
| q = range.length; | |
| thresholds = []; | |
| while (++k < q) thresholds[k - 1] = d3.quantile(domain, k / q); | |
| return scale; | |
| } | |
| function scale(x) { | |
| if (isNaN(x = +x)) return NaN; | |
| return range[d3.bisect(thresholds, x)]; | |
| } | |
| scale.domain = function(x) { | |
| if (!arguments.length) return domain; | |
| domain = x.filter(function(d) { return !isNaN(d); }).sort(d3.ascending); | |
| return rescale(); | |
| }; | |
| scale.range = function(x) { | |
| if (!arguments.length) return range; | |
| range = x; | |
| return rescale(); | |
| }; | |
| scale.quantiles = function() { | |
| return thresholds; | |
| }; | |
| scale.ticks = function(m) { | |
| return thresholds; | |
| }; | |
| scale.copy = function() { | |
| return d3_scale_quantile(domain, range); // copy on write! | |
| }; | |
| return rescale(); | |
| } | |
| d3.scale.quantize = function() { | |
| return d3_scale_quantize(0, 1, [0, 1]); | |
| }; | |
| function d3_scale_quantize(x0, x1, range) { | |
| var kx, i; | |
| function scale(x) { | |
| return range[Math.max(0, Math.min(i, Math.floor(kx * (x - x0))))]; | |
| } | |
| function rescale() { | |
| kx = range.length / (x1 - x0); | |
| i = range.length - 1; | |
| return scale; | |
| } | |
| scale.domain = function(x) { | |
| if (!arguments.length) return [x0, x1]; | |
| x0 = +x[0]; | |
| x1 = +x[x.length - 1]; | |
| return rescale(); | |
| }; | |
| scale.range = function(x) { | |
| if (!arguments.length) return range; | |
| range = x; | |
| return rescale(); | |
| }; | |
| scale.ticks = function(m) { | |
| if (i <= 0) return []; | |
| // produce nice tick values (erase the long decimal tails due to floating point calc inaccuracy), | |
| // x1 is not inclusive | |
| return d3.range(x0, x1 - 0.5 / kx, 1.0 / kx).map(function(x, i) { | |
| // heuristic: round to 3 extra digits to remove FP calc inaccuracy | |
| var p = d3_format_precision(x, 4); | |
| var v = d3.round(x, p); | |
| return v; | |
| }); | |
| }; | |
| scale.rangeBand = function() { | |
| return kx; | |
| } | |
| scale.copy = function() { | |
| return d3_scale_quantize(x0, x1, range); // copy on write | |
| }; | |
| return rescale(); | |
| } | |
| d3.scale.threshold = function() { | |
| return d3_scale_threshold([.5], [0, 1]); | |
| }; | |
| function d3_scale_threshold(domain, range) { | |
| function scale(x) { | |
| // ASSUMPTION: domain.length == range.length - 1 | |
| return range[d3.bisect(domain, x)]; | |
| } | |
| scale.domain = function(_) { | |
| if (!arguments.length) return domain; | |
| domain = _; | |
| return scale; | |
| }; | |
| scale.range = function(_) { | |
| if (!arguments.length) return range; | |
| range = _; | |
| return scale; | |
| }; | |
| scale.ticks = function(m) { | |
| var l = Math.min(domain.length, range.length - 1); | |
| if (l > 0) { | |
| var t = [], i; | |
| t.push(+domain[0] - 1); | |
| for (i = 1; i < l; i++) { | |
| t.push((+domain[i] + +domain[i - 1]) / 2); | |
| } | |
| t.push(+domain[l - 1] + 1); | |
| return t; | |
| } | |
| return []; | |
| }; | |
| scale.copy = function() { | |
| return d3_scale_threshold(domain, range); | |
| }; | |
| return scale; | |
| }; | |
| d3.scale.identity = function() { | |
| return d3_scale_identity([0, 1]); | |
| }; | |
| function d3_scale_identity(domain) { | |
| function identity(x) { return +x; } | |
| identity.invert = identity; | |
| identity.domain = identity.range = function(x) { | |
| if (!arguments.length) return domain; | |
| domain = x.map(identity); | |
| return identity; | |
| }; | |
| identity.ticks = function(m) { | |
| return d3_scale_linearTicks(domain, m); | |
| }; | |
| identity.tickFormat = function(m) { | |
| return d3_scale_linearTickFormat(domain, m); | |
| }; | |
| identity.copy = function() { | |
| return d3_scale_identity(domain); | |
| }; | |
| return identity; | |
| } | |
| d3.svg = {}; | |
| d3.svg.arc = function() { | |
| var innerRadius = d3_svg_arcInnerRadius, | |
| outerRadius = d3_svg_arcOuterRadius, | |
| startAngle = d3_svg_arcStartAngle, | |
| endAngle = d3_svg_arcEndAngle; | |
| function arc() { | |
| var r0 = innerRadius.apply(this, arguments), | |
| r1 = outerRadius.apply(this, arguments), | |
| a0 = startAngle.apply(this, arguments) + d3_svg_arcOffset, | |
| a1 = endAngle.apply(this, arguments) + d3_svg_arcOffset, | |
| da = (a1 < a0 && (da = a0, a0 = a1, a1 = da), a1 - a0), | |
| df = da < Math.PI ? "0" : "1", | |
| c0 = Math.cos(a0), | |
| s0 = Math.sin(a0), | |
| c1 = Math.cos(a1), | |
| s1 = Math.sin(a1); | |
| return da >= d3_svg_arcMax | |
| ? (r0 | |
| ? "M0," + r1 | |
| + "A" + r1 + "," + r1 + " 0 1,1 0," + (-r1) | |
| + "A" + r1 + "," + r1 + " 0 1,1 0," + r1 | |
| + "M0," + r0 | |
| + "A" + r0 + "," + r0 + " 0 1,0 0," + (-r0) | |
| + "A" + r0 + "," + r0 + " 0 1,0 0," + r0 | |
| + "Z" | |
| : "M0," + r1 | |
| + "A" + r1 + "," + r1 + " 0 1,1 0," + (-r1) | |
| + "A" + r1 + "," + r1 + " 0 1,1 0," + r1 | |
| + "Z") | |
| : (r0 | |
| ? "M" + r1 * c0 + "," + r1 * s0 | |
| + "A" + r1 + "," + r1 + " 0 " + df + ",1 " + r1 * c1 + "," + r1 * s1 | |
| + "L" + r0 * c1 + "," + r0 * s1 | |
| + "A" + r0 + "," + r0 + " 0 " + df + ",0 " + r0 * c0 + "," + r0 * s0 | |
| + "Z" | |
| : "M" + r1 * c0 + "," + r1 * s0 | |
| + "A" + r1 + "," + r1 + " 0 " + df + ",1 " + r1 * c1 + "," + r1 * s1 | |
| + "L0,0" | |
| + "Z"); | |
| } | |
| arc.innerRadius = function(v) { | |
| if (!arguments.length) return innerRadius; | |
| innerRadius = d3_functor(v); | |
| return arc; | |
| }; | |
| arc.outerRadius = function(v) { | |
| if (!arguments.length) return outerRadius; | |
| outerRadius = d3_functor(v); | |
| return arc; | |
| }; | |
| arc.startAngle = function(v) { | |
| if (!arguments.length) return startAngle; | |
| startAngle = d3_functor(v); | |
| return arc; | |
| }; | |
| arc.endAngle = function(v) { | |
| if (!arguments.length) return endAngle; | |
| endAngle = d3_functor(v); | |
| return arc; | |
| }; | |
| arc.centroid = function() { | |
| var r = (innerRadius.apply(this, arguments) | |
| + outerRadius.apply(this, arguments)) / 2, | |
| a = (startAngle.apply(this, arguments) | |
| + endAngle.apply(this, arguments)) / 2 + d3_svg_arcOffset; | |
| return [Math.cos(a) * r, Math.sin(a) * r]; | |
| }; | |
| return arc; | |
| }; | |
| var d3_svg_arcOffset = -Math.PI / 2, | |
| d3_svg_arcMax = 2 * Math.PI - 1e-6; | |
| function d3_svg_arcInnerRadius(d) { | |
| return d.innerRadius; | |
| } | |
| function d3_svg_arcOuterRadius(d) { | |
| return d.outerRadius; | |
| } | |
| function d3_svg_arcStartAngle(d) { | |
| return d.startAngle; | |
| } | |
| function d3_svg_arcEndAngle(d) { | |
| return d.endAngle; | |
| } | |
| function d3_svg_line(projection) { | |
| var x = d3_svg_lineX, | |
| y = d3_svg_lineY, | |
| defined = d3_true, | |
| interpolate = d3_svg_lineLinear, | |
| interpolateKey = interpolate.key, | |
| tension = .7; | |
| function line(data) { | |
| var segments = [], | |
| points = [], | |
| i = -1, | |
| n = data.length, | |
| d, | |
| fx = d3_functor(x), | |
| fy = d3_functor(y); | |
| function segment() { | |
| segments.push("M", interpolate(projection(points), tension)); | |
| } | |
| while (++i < n) { | |
| if (defined.call(this, d = data[i], i)) { | |
| points.push([+fx.call(this, d, i), +fy.call(this, d, i)]); | |
| } else if (points.length) { | |
| segment(); | |
| points = []; | |
| } | |
| } | |
| if (points.length) segment(); | |
| return segments.length ? segments.join("") : null; | |
| } | |
| line.x = function(_) { | |
| if (!arguments.length) return x; | |
| x = _; | |
| return line; | |
| }; | |
| line.y = function(_) { | |
| if (!arguments.length) return y; | |
| y = _; | |
| return line; | |
| }; | |
| line.defined = function(_) { | |
| if (!arguments.length) return defined; | |
| defined = _; | |
| return line; | |
| }; | |
| line.interpolate = function(_) { | |
| if (!arguments.length) return interpolateKey; | |
| if (typeof _ === "function") interpolateKey = interpolate = _; | |
| else interpolateKey = (interpolate = d3_svg_lineInterpolators.get(_) || d3_svg_lineLinear).key; | |
| return line; | |
| }; | |
| line.tension = function(_) { | |
| if (!arguments.length) return tension; | |
| tension = _; | |
| return line; | |
| }; | |
| return line; | |
| } | |
| d3.svg.line = function() { | |
| return d3_svg_line(d3_identity); | |
| }; | |
| // The default `x` property, which references d[0]. | |
| function d3_svg_lineX(d) { | |
| return d[0]; | |
| } | |
| // The default `y` property, which references d[1]. | |
| function d3_svg_lineY(d) { | |
| return d[1]; | |
| } | |
| // The various interpolators supported by the `line` class. | |
| var d3_svg_lineInterpolators = d3.map({ | |
| "linear": d3_svg_lineLinear, | |
| "linear-closed": d3_svg_lineLinearClosed, | |
| "step-before": d3_svg_lineStepBefore, | |
| "step-after": d3_svg_lineStepAfter, | |
| "basis": d3_svg_lineBasis, | |
| "basis-open": d3_svg_lineBasisOpen, | |
| "basis-closed": d3_svg_lineBasisClosed, | |
| "bundle": d3_svg_lineBundle, | |
| "cardinal": d3_svg_lineCardinal, | |
| "cardinal-open": d3_svg_lineCardinalOpen, | |
| "cardinal-closed": d3_svg_lineCardinalClosed, | |
| "monotone": d3_svg_lineMonotone | |
| }); | |
| d3_svg_lineInterpolators.forEach(function(key, value) { | |
| value.key = key; | |
| value.closed = /-closed$/.test(key); | |
| }); | |
| // Linear interpolation; generates "L" commands. | |
| function d3_svg_lineLinear(points) { | |
| return points.join("L"); | |
| } | |
| function d3_svg_lineLinearClosed(points) { | |
| return d3_svg_lineLinear(points) + "Z"; | |
| } | |
| // Step interpolation; generates "H" and "V" commands. | |
| function d3_svg_lineStepBefore(points) { | |
| var i = 0, | |
| n = points.length, | |
| p = points[0], | |
| path = [p[0], ",", p[1]]; | |
| while (++i < n) path.push("V", (p = points[i])[1], "H", p[0]); | |
| return path.join(""); | |
| } | |
| // Step interpolation; generates "H" and "V" commands. | |
| function d3_svg_lineStepAfter(points) { | |
| var i = 0, | |
| n = points.length, | |
| p = points[0], | |
| path = [p[0], ",", p[1]]; | |
| while (++i < n) path.push("H", (p = points[i])[0], "V", p[1]); | |
| return path.join(""); | |
| } | |
| // Open cardinal spline interpolation; generates "C" commands. | |
| function d3_svg_lineCardinalOpen(points, tension) { | |
| return points.length < 4 | |
| ? d3_svg_lineLinear(points) | |
| : points[1] + d3_svg_lineHermite(points.slice(1, points.length - 1), | |
| d3_svg_lineCardinalTangents(points, tension)); | |
| } | |
| // Closed cardinal spline interpolation; generates "C" commands. | |
| function d3_svg_lineCardinalClosed(points, tension) { | |
| return points.length < 3 | |
| ? d3_svg_lineLinear(points) | |
| : points[0] + d3_svg_lineHermite((points.push(points[0]), points), | |
| d3_svg_lineCardinalTangents([points[points.length - 2]] | |
| .concat(points, [points[1]]), tension)); | |
| } | |
| // Cardinal spline interpolation; generates "C" commands. | |
| function d3_svg_lineCardinal(points, tension, closed) { | |
| return points.length < 3 | |
| ? d3_svg_lineLinear(points) | |
| : points[0] + d3_svg_lineCubicPolynomialSpline(points, | |
| d3_svg_lineCardinalTangents(points, tension)); | |
| } | |
| // Hermite spline construction; generates "C" commands. | |
| function d3_svg_lineHermite(points, tangents) { | |
| if (tangents.length < 1 | |
| || (points.length != tangents.length | |
| && points.length != tangents.length + 2)) { | |
| return d3_svg_lineLinear(points); | |
| } | |
| var quad = points.length != tangents.length, | |
| path = "", | |
| p0 = points[0], | |
| p = points[1], | |
| t0 = tangents[0], | |
| t = t0, | |
| pi = 1; | |
| if (quad) { | |
| path += "Q" + (p[0] - t0[0] * 2 / 3) + "," + (p[1] - t0[1] * 2 / 3) | |
| + "," + p[0] + "," + p[1]; | |
| p0 = points[1]; | |
| pi = 2; | |
| } | |
| if (tangents.length > 1) { | |
| t = tangents[1]; | |
| p = points[pi]; | |
| pi++; | |
| path += "C" + (p0[0] + t0[0]) + "," + (p0[1] + t0[1]) | |
| + "," + (p[0] - t[0]) + "," + (p[1] - t[1]) | |
| + "," + p[0] + "," + p[1]; | |
| for (var i = 2; i < tangents.length; i++, pi++) { | |
| p = points[pi]; | |
| t = tangents[i]; | |
| path += "S" + (p[0] - t[0]) + "," + (p[1] - t[1]) | |
| + "," + p[0] + "," + p[1]; | |
| } | |
| } | |
| if (quad) { | |
| var lp = points[pi]; | |
| path += "Q" + (p[0] + t[0] * 2 / 3) + "," + (p[1] + t[1] * 2 / 3) | |
| + "," + lp[0] + "," + lp[1]; | |
| } | |
| return path; | |
| } | |
| // Cubic polynomial spline construction; generates "C" commands. | |
| function d3_svg_lineCubicPolynomialSpline(points, tangents) { | |
| if (tangents.length < 1 | |
| || (points.length != tangents.length | |
| && points.length != tangents.length + 2)) { | |
| return d3_svg_lineLinear(points); | |
| } | |
| var quad = points.length != tangents.length, | |
| path = "", | |
| p0 = points[0], | |
| p = points[1], | |
| t0 = tangents[0], | |
| t = t0, | |
| pi = 0, | |
| dx = points[1][0] - points[0][0], | |
| tp, pp; | |
| if (quad) { | |
| path += "Q" + (p[0] - (dx / 2)) + "," + (p[1] - t0[1]/t0[0] * (dx / 2)) | |
| + "," + p[0] + "," + p[1]; | |
| p0 = points[1]; | |
| pi = 1; | |
| } | |
| if (tangents.length > 1) { | |
| tp = tangents[0]; | |
| pp = points[pi]; | |
| pi++; | |
| for (var i = 1; i < tangents.length; i++, pi++) { | |
| p = points[pi]; t = tangents[i]; | |
| dx = p[0] - pp[0]; | |
| path += "C" + (pp[0] + (dx / 3)) + "," + (pp[1] + (tp[1]/tp[0]) * (dx / 3)) | |
| + "," + (p[0] - (dx / 3)) + "," + (p[1] - (t[1]/t[0]) * (dx / 3)) | |
| + "," + p[0] + "," + p[1]; | |
| pp = p; tp = t; | |
| } | |
| } | |
| if (quad) { | |
| var lp = points[pi]; | |
| dx = lp[0] - p[0]; | |
| path += "Q" + (p[0] + (dx / 2)) + "," + (p[1] + t[1]/t[0] * (dx / 2)) | |
| + "," + lp[0] + "," + lp[1]; | |
| } | |
| return path; | |
| } | |
| // Generates tangents for a cardinal spline. | |
| function d3_svg_lineCardinalTangents(points, tension) { | |
| var tangents = [], | |
| a = (1 - tension) / 2, | |
| p0, | |
| p1 = points[0], | |
| p2 = points[1], | |
| i = 1, | |
| n = points.length; | |
| while (++i < n) { | |
| p0 = p1; | |
| p1 = p2; | |
| p2 = points[i]; | |
| tangents.push([a * (p2[0] - p0[0]), a * (p2[1] - p0[1])]); | |
| } | |
| return tangents; | |
| } | |
| // B-spline interpolation; generates "C" commands. | |
| function d3_svg_lineBasis(points) { | |
| if (points.length < 3) return d3_svg_lineLinear(points); | |
| var i = 1, | |
| n = points.length, | |
| pi = points[0], | |
| x0 = pi[0], | |
| y0 = pi[1], | |
| px = [x0, x0, x0, (pi = points[1])[0]], | |
| py = [y0, y0, y0, pi[1]], | |
| path = [x0, ",", y0]; | |
| d3_svg_lineBasisBezier(path, px, py); | |
| while (++i < n) { | |
| pi = points[i]; | |
| px.shift(); px.push(pi[0]); | |
| py.shift(); py.push(pi[1]); | |
| d3_svg_lineBasisBezier(path, px, py); | |
| } | |
| i = -1; | |
| while (++i < 2) { | |
| px.shift(); px.push(pi[0]); | |
| py.shift(); py.push(pi[1]); | |
| d3_svg_lineBasisBezier(path, px, py); | |
| } | |
| return path.join(""); | |
| } | |
| // Open B-spline interpolation; generates "C" commands. | |
| function d3_svg_lineBasisOpen(points) { | |
| if (points.length < 4) return d3_svg_lineLinear(points); | |
| var path = [], | |
| i = -1, | |
| n = points.length, | |
| pi, | |
| px = [0], | |
| py = [0]; | |
| while (++i < 3) { | |
| pi = points[i]; | |
| px.push(pi[0]); | |
| py.push(pi[1]); | |
| } | |
| path.push(d3_svg_lineDot4(d3_svg_lineBasisBezier3, px) | |
| + "," + d3_svg_lineDot4(d3_svg_lineBasisBezier3, py)); | |
| --i; while (++i < n) { | |
| pi = points[i]; | |
| px.shift(); px.push(pi[0]); | |
| py.shift(); py.push(pi[1]); | |
| d3_svg_lineBasisBezier(path, px, py); | |
| } | |
| return path.join(""); | |
| } | |
| // Closed B-spline interpolation; generates "C" commands. | |
| function d3_svg_lineBasisClosed(points) { | |
| var path, | |
| i = -1, | |
| n = points.length, | |
| m = n + 4, | |
| pi, | |
| px = [], | |
| py = []; | |
| while (++i < 4) { | |
| pi = points[i % n]; | |
| px.push(pi[0]); | |
| py.push(pi[1]); | |
| } | |
| path = [ | |
| d3_svg_lineDot4(d3_svg_lineBasisBezier3, px), ",", | |
| d3_svg_lineDot4(d3_svg_lineBasisBezier3, py) | |
| ]; | |
| --i; while (++i < m) { | |
| pi = points[i % n]; | |
| px.shift(); px.push(pi[0]); | |
| py.shift(); py.push(pi[1]); | |
| d3_svg_lineBasisBezier(path, px, py); | |
| } | |
| return path.join(""); | |
| } | |
| function d3_svg_lineBundle(points, tension) { | |
| var n = points.length - 1; | |
| if (n) { | |
| var x0 = points[0][0], | |
| y0 = points[0][1], | |
| dx = points[n][0] - x0, | |
| dy = points[n][1] - y0, | |
| i = -1, | |
| p, | |
| t; | |
| while (++i <= n) { | |
| p = points[i]; | |
| t = i / n; | |
| p[0] = tension * p[0] + (1 - tension) * (x0 + t * dx); | |
| p[1] = tension * p[1] + (1 - tension) * (y0 + t * dy); | |
| } | |
| } | |
| return d3_svg_lineBasis(points); | |
| } | |
| // Returns the dot product of the given four-element vectors. | |
| function d3_svg_lineDot4(a, b) { | |
| return a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3]; | |
| } | |
| // Matrix to transform basis (b-spline) control points to bezier | |
| // control points. Derived from FvD 11.2.8. | |
| var d3_svg_lineBasisBezier1 = [0, 2/3, 1/3, 0], | |
| d3_svg_lineBasisBezier2 = [0, 1/3, 2/3, 0], | |
| d3_svg_lineBasisBezier3 = [0, 1/6, 2/3, 1/6]; | |
| // Pushes a "C" Bézier curve onto the specified path array, given the | |
| // two specified four-element arrays which define the control points. | |
| function d3_svg_lineBasisBezier(path, x, y) { | |
| path.push( | |
| "C", d3_svg_lineDot4(d3_svg_lineBasisBezier1, x), | |
| ",", d3_svg_lineDot4(d3_svg_lineBasisBezier1, y), | |
| ",", d3_svg_lineDot4(d3_svg_lineBasisBezier2, x), | |
| ",", d3_svg_lineDot4(d3_svg_lineBasisBezier2, y), | |
| ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, x), | |
| ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, y)); | |
| } | |
| // Computes the slope from points p0 to p1. | |
| function d3_svg_lineSlope(p0, p1) { | |
| return (p1[1] - p0[1]) / (p1[0] - p0[0]); | |
| } | |
| // Compute three-point differences for the given points. | |
| // http://en.wikipedia.org/wiki/Cubic_Hermite_spline#Finite_difference | |
| function d3_svg_lineFiniteDifferences(points) { | |
| var i = 0, | |
| j = points.length - 1, | |
| m = [], | |
| p0 = points[0], | |
| p1 = points[1], | |
| d = m[0] = d3_svg_lineSlope(p0, p1); | |
| while (++i < j) { | |
| m[i] = (d + (d = d3_svg_lineSlope(p0 = p1, p1 = points[i + 1]))) / 2; | |
| } | |
| m[i] = d; | |
| return m; | |
| } | |
| // Interpolates the given points using Fritsch-Carlson Monotone cubic Hermite | |
| // interpolation. Returns an array of tangent vectors. For details, see | |
| // http://en.wikipedia.org/wiki/Monotone_cubic_interpolation | |
| function d3_svg_lineMonotoneTangents(points) { | |
| var tangents = [], | |
| d, | |
| a, | |
| b, | |
| s, | |
| m = d3_svg_lineFiniteDifferences(points), | |
| i = -1, | |
| j = points.length - 1; | |
| // The first two steps are done by computing finite-differences: | |
| // 1. Compute the slopes of the secant lines between successive points. | |
| // 2. Initialize the tangents at every point as the average of the secants. | |
| // Then, for each segment… | |
| while (++i < j) { | |
| d = d3_svg_lineSlope(points[i], points[i + 1]); | |
| // 3. If two successive yk = y{k + 1} are equal (i.e., d is zero), then set | |
| // mk = m{k + 1} = 0 as the spline connecting these points must be flat to | |
| // preserve monotonicity. Ignore step 4 and 5 for those k. | |
| if (Math.abs(d) < 1e-6) { | |
| m[i] = m[i + 1] = 0; | |
| } else { | |
| // 4. Let ak = mk / dk and bk = m{k + 1} / dk. | |
| a = m[i] / d; | |
| b = m[i + 1] / d; | |
| // 5. Prevent overshoot and ensure monotonicity by restricting the | |
| // magnitude of vector <ak, bk> to a circle of radius 3. | |
| s = a * a + b * b; | |
| if (s > 9) { | |
| s = d * 3 / Math.sqrt(s); | |
| m[i] = s * a; | |
| m[i + 1] = s * b; | |
| } | |
| } | |
| } | |
| // Compute the normalized tangent vector from the slopes. Note that if x is | |
| // not monotonic, it's possible that the slope will be infinite, so we protect | |
| // against NaN by setting the coordinate to zero. | |
| i = -1; while (++i <= j) { | |
| s = (points[Math.min(j, i + 1)][0] - points[Math.max(0, i - 1)][0]) / (6 * (1 + m[i] * m[i])); | |
| tangents.push([s || 0, m[i] * s || 0]); | |
| } | |
| return tangents; | |
| } | |
| function d3_svg_lineMonotone(points) { | |
| return points.length < 3 | |
| ? d3_svg_lineLinear(points) | |
| : points[0] + d3_svg_lineCubicPolynomialSpline(points, d3_svg_lineMonotoneTangents(points)); | |
| } | |
| d3.svg.line.radial = function() { | |
| var line = d3_svg_line(d3_svg_lineRadial); | |
| line.radius = line.x, delete line.x; | |
| line.angle = line.y, delete line.y; | |
| return line; | |
| }; | |
| function d3_svg_lineRadial(points) { | |
| var point, | |
| i = -1, | |
| n = points.length, | |
| r, | |
| a; | |
| while (++i < n) { | |
| point = points[i]; | |
| r = point[0]; | |
| a = point[1] + d3_svg_arcOffset; | |
| point[0] = r * Math.cos(a); | |
| point[1] = r * Math.sin(a); | |
| } | |
| return points; | |
| } | |
| function d3_svg_area(projection) { | |
| var x0 = d3_svg_lineX, | |
| x1 = d3_svg_lineX, | |
| y0 = 0, | |
| y1 = d3_svg_lineY, | |
| defined = d3_true, | |
| interpolate = d3_svg_lineLinear, | |
| interpolateKey = interpolate.key, | |
| interpolateReverse = interpolate, | |
| L = "L", | |
| tension = .7; | |
| function area(data) { | |
| var segments = [], | |
| points0 = [], | |
| points1 = [], | |
| i = -1, | |
| n = data.length, | |
| d, | |
| fx0 = d3_functor(x0), | |
| fy0 = d3_functor(y0), | |
| fx1 = x0 === x1 ? function() { return x; } : d3_functor(x1), | |
| fy1 = y0 === y1 ? function() { return y; } : d3_functor(y1), | |
| x, | |
| y; | |
| function segment() { | |
| segments.push("M", interpolate(projection(points1), tension), | |
| L, interpolateReverse(projection(points0.reverse()), tension), | |
| "Z"); | |
| } | |
| while (++i < n) { | |
| if (defined.call(this, d = data[i], i)) { | |
| points0.push([x = +fx0.call(this, d, i), y = +fy0.call(this, d, i)]); | |
| points1.push([+fx1.call(this, d, i), +fy1.call(this, d, i)]); | |
| } else if (points0.length) { | |
| segment(); | |
| points0 = []; | |
| points1 = []; | |
| } | |
| } | |
| if (points0.length) segment(); | |
| return segments.length ? segments.join("") : null; | |
| } | |
| area.x = function(_) { | |
| if (!arguments.length) return x1; | |
| x0 = x1 = _; | |
| return area; | |
| }; | |
| area.x0 = function(_) { | |
| if (!arguments.length) return x0; | |
| x0 = _; | |
| return area; | |
| }; | |
| area.x1 = function(_) { | |
| if (!arguments.length) return x1; | |
| x1 = _; | |
| return area; | |
| }; | |
| area.y = function(_) { | |
| if (!arguments.length) return y1; | |
| y0 = y1 = _; | |
| return area; | |
| }; | |
| area.y0 = function(_) { | |
| if (!arguments.length) return y0; | |
| y0 = _; | |
| return area; | |
| }; | |
| area.y1 = function(_) { | |
| if (!arguments.length) return y1; | |
| y1 = _; | |
| return area; | |
| }; | |
| area.defined = function(_) { | |
| if (!arguments.length) return defined; | |
| defined = _; | |
| return area; | |
| }; | |
| area.interpolate = function(_) { | |
| if (!arguments.length) return interpolateKey; | |
| if (typeof _ === "function") interpolateKey = interpolate = _; | |
| else interpolateKey = (interpolate = d3_svg_lineInterpolators.get(_) || d3_svg_lineLinear).key; | |
| interpolateReverse = interpolate.reverse || interpolate; | |
| L = interpolate.closed ? "M" : "L"; | |
| return area; | |
| }; | |
| area.tension = function(_) { | |
| if (!arguments.length) return tension; | |
| tension = _; | |
| return area; | |
| }; | |
| return area; | |
| } | |
| d3_svg_lineStepBefore.reverse = d3_svg_lineStepAfter; | |
| d3_svg_lineStepAfter.reverse = d3_svg_lineStepBefore; | |
| d3.svg.area = function() { | |
| return d3_svg_area(d3_identity); | |
| }; | |
| d3.svg.area.radial = function() { | |
| var area = d3_svg_area(d3_svg_lineRadial); | |
| area.radius = area.x, delete area.x; | |
| area.innerRadius = area.x0, delete area.x0; | |
| area.outerRadius = area.x1, delete area.x1; | |
| area.angle = area.y, delete area.y; | |
| area.startAngle = area.y0, delete area.y0; | |
| area.endAngle = area.y1, delete area.y1; | |
| return area; | |
| }; | |
| d3.svg.chord = function() { | |
| var source = d3_svg_chordSource, | |
| target = d3_svg_chordTarget, | |
| radius = d3_svg_chordRadius, | |
| startAngle = d3_svg_arcStartAngle, | |
| endAngle = d3_svg_arcEndAngle; | |
| // TODO Allow control point to be customized. | |
| function chord(d, i) { | |
| var s = subgroup(this, source, d, i), | |
| t = subgroup(this, target, d, i); | |
| return "M" + s.p0 | |
| + arc(s.r, s.p1, s.a1 - s.a0) + (equals(s, t) | |
| ? curve(s.r, s.p1, s.r, s.p0) | |
| : curve(s.r, s.p1, t.r, t.p0) | |
| + arc(t.r, t.p1, t.a1 - t.a0) | |
| + curve(t.r, t.p1, s.r, s.p0)) | |
| + "Z"; | |
| } | |
| function subgroup(self, f, d, i) { | |
| var subgroup = f.call(self, d, i), | |
| r = radius.call(self, subgroup, i), | |
| a0 = startAngle.call(self, subgroup, i) + d3_svg_arcOffset, | |
| a1 = endAngle.call(self, subgroup, i) + d3_svg_arcOffset; | |
| return { | |
| r: r, | |
| a0: a0, | |
| a1: a1, | |
| p0: [r * Math.cos(a0), r * Math.sin(a0)], | |
| p1: [r * Math.cos(a1), r * Math.sin(a1)] | |
| }; | |
| } | |
| function equals(a, b) { | |
| return a.a0 == b.a0 && a.a1 == b.a1; | |
| } | |
| function arc(r, p, a) { | |
| return "A" + r + "," + r + " 0 " + +(a > Math.PI) + ",1 " + p; | |
| } | |
| function curve(r0, p0, r1, p1) { | |
| return "Q 0,0 " + p1; | |
| } | |
| chord.radius = function(v) { | |
| if (!arguments.length) return radius; | |
| radius = d3_functor(v); | |
| return chord; | |
| }; | |
| chord.source = function(v) { | |
| if (!arguments.length) return source; | |
| source = d3_functor(v); | |
| return chord; | |
| }; | |
| chord.target = function(v) { | |
| if (!arguments.length) return target; | |
| target = d3_functor(v); | |
| return chord; | |
| }; | |
| chord.startAngle = function(v) { | |
| if (!arguments.length) return startAngle; | |
| startAngle = d3_functor(v); | |
| return chord; | |
| }; | |
| chord.endAngle = function(v) { | |
| if (!arguments.length) return endAngle; | |
| endAngle = d3_functor(v); | |
| return chord; | |
| }; | |
| return chord; | |
| }; | |
| function d3_svg_chordSource(d) { | |
| return d.source; | |
| } | |
| function d3_svg_chordTarget(d) { | |
| return d.target; | |
| } | |
| function d3_svg_chordRadius(d) { | |
| return d.radius; | |
| } | |
| function d3_svg_chordStartAngle(d) { | |
| return d.startAngle; | |
| } | |
| function d3_svg_chordEndAngle(d) { | |
| return d.endAngle; | |
| } | |
| d3.svg.diagonal = function() { | |
| var source = d3_svg_chordSource, | |
| target = d3_svg_chordTarget, | |
| projection = d3_svg_diagonalProjection; | |
| function diagonal(d, i) { | |
| var p0 = source.call(this, d, i), | |
| p3 = target.call(this, d, i), | |
| m = (p0.y + p3.y) / 2, | |
| p = [p0, {x: p0.x, y: m}, {x: p3.x, y: m}, p3]; | |
| p = p.map(projection); | |
| return "M" + p[0] + "C" + p[1] + " " + p[2] + " " + p[3]; | |
| } | |
| diagonal.source = function(x) { | |
| if (!arguments.length) return source; | |
| source = d3_functor(x); | |
| return diagonal; | |
| }; | |
| diagonal.target = function(x) { | |
| if (!arguments.length) return target; | |
| target = d3_functor(x); | |
| return diagonal; | |
| }; | |
| diagonal.projection = function(x) { | |
| if (!arguments.length) return projection; | |
| projection = x; | |
| return diagonal; | |
| }; | |
| return diagonal; | |
| }; | |
| function d3_svg_diagonalProjection(d) { | |
| return [d.x, d.y]; | |
| } | |
| d3.svg.diagonal.radial = function() { | |
| var diagonal = d3.svg.diagonal(), | |
| projection = d3_svg_diagonalProjection, | |
| projection_ = diagonal.projection; | |
| diagonal.projection = function(x) { | |
| return arguments.length | |
| ? projection_(d3_svg_diagonalRadialProjection(projection = x)) | |
| : projection; | |
| }; | |
| return diagonal; | |
| }; | |
| function d3_svg_diagonalRadialProjection(projection) { | |
| return function() { | |
| var d = projection.apply(this, arguments), | |
| r = d[0], | |
| a = d[1] + d3_svg_arcOffset; | |
| return [r * Math.cos(a), r * Math.sin(a)]; | |
| }; | |
| } | |
| d3.svg.mouse = d3.mouse; | |
| d3.svg.touches = d3.touches; | |
| d3.svg.symbol = function() { | |
| var type = d3_svg_symbolType, | |
| size = d3_svg_symbolSize; | |
| function symbol(d, i) { | |
| return (d3_svg_symbols.get(type.call(this, d, i)) | |
| || d3_svg_symbolCircle) | |
| (size.call(this, d, i)); | |
| } | |
| symbol.type = function(x) { | |
| if (!arguments.length) return type; | |
| type = d3_functor(x); | |
| return symbol; | |
| }; | |
| // size of symbol in square pixels | |
| symbol.size = function(x) { | |
| if (!arguments.length) return size; | |
| size = d3_functor(x); | |
| return symbol; | |
| }; | |
| return symbol; | |
| }; | |
| function d3_svg_symbolSize() { | |
| return 64; | |
| } | |
| function d3_svg_symbolType() { | |
| return "circle"; | |
| } | |
| function d3_svg_symbolCircle(size) { | |
| var r = Math.sqrt(size / Math.PI); | |
| return "M0," + r | |
| + "A" + r + "," + r + " 0 1,1 0," + (-r) | |
| + "A" + r + "," + r + " 0 1,1 0," + r | |
| + "Z"; | |
| } | |
| // TODO cross-diagonal? | |
| var d3_svg_symbols = d3.map({ | |
| "circle": d3_svg_symbolCircle, | |
| "cross": function(size) { | |
| var r = Math.sqrt(size / 5) / 2; | |
| return "M" + -3 * r + "," + -r | |
| + "H" + -r | |
| + "V" + -3 * r | |
| + "H" + r | |
| + "V" + -r | |
| + "H" + 3 * r | |
| + "V" + r | |
| + "H" + r | |
| + "V" + 3 * r | |
| + "H" + -r | |
| + "V" + r | |
| + "H" + -3 * r | |
| + "Z"; | |
| }, | |
| "diamond": function(size) { | |
| var ry = Math.sqrt(size / (2 * d3_svg_symbolTan30)), | |
| rx = ry * d3_svg_symbolTan30; | |
| return "M0," + -ry | |
| + "L" + rx + ",0" | |
| + " 0," + ry | |
| + " " + -rx + ",0" | |
| + "Z"; | |
| }, | |
| "square": function(size) { | |
| var r = Math.sqrt(size) / 2; | |
| return "M" + -r + "," + -r | |
| + "L" + r + "," + -r | |
| + " " + r + "," + r | |
| + " " + -r + "," + r | |
| + "Z"; | |
| }, | |
| "triangle-down": function(size) { | |
| var rx = Math.sqrt(size / d3_svg_symbolSqrt3), | |
| ry = rx * d3_svg_symbolSqrt3 / 2; | |
| return "M0," + ry | |
| + "L" + rx +"," + -ry | |
| + " " + -rx + "," + -ry | |
| + "Z"; | |
| }, | |
| "triangle-up": function(size) { | |
| var rx = Math.sqrt(size / d3_svg_symbolSqrt3), | |
| ry = rx * d3_svg_symbolSqrt3 / 2; | |
| return "M0," + -ry | |
| + "L" + rx +"," + ry | |
| + " " + -rx + "," + ry | |
| + "Z"; | |
| } | |
| }); | |
| d3.svg.symbolTypes = d3_svg_symbols.keys(); | |
| var d3_svg_symbolSqrt3 = Math.sqrt(3), | |
| d3_svg_symbolTan30 = Math.tan(30 * Math.PI / 180); | |
| d3.svg.axis = function() { | |
| var scale = d3.scale.linear(), | |
| orient = "bottom", | |
| tickMajorSize = d3_functor(6), | |
| tickMinorSize = d3_functor(6), | |
| tickEndSize = d3_functor(6), | |
| tickPadding = 3, | |
| tickArguments_ = [10], | |
| tickValues = null, | |
| tickFormat_ = null, | |
| tickFormatExtended_, | |
| tickFilter = d3_functor(true), | |
| tickSubdivide = null; | |
| function axis(g) { | |
| // Ticks, or domain values for ordinal scales. | |
| var ticks = (tickValues == null ? (scale.ticks ? scale.ticks.apply(scale, tickArguments_) : scale.domain()) : tickValues) | |
| .map(d3_svg_axisMapTicks), | |
| tickFormat = tickFormat_ == null ? (scale.tickFormat ? scale.tickFormat.apply(scale, tickArguments_) : String) : tickFormat_; | |
| // Minor ticks. | |
| var subticks = d3_svg_axisSubdivide(scale, ticks, tickSubdivide); | |
| subticks = subticks.filter(function(d, i, a) { | |
| return tickFilter(d, d.index, ticks, i, a); | |
| }); | |
| var range = d3_scaleRange(scale); | |
| if (g) { | |
| g.each(function() { | |
| var g = d3.select(this); | |
| var subtick = g.selectAll(".minor"); | |
| subtick = subtick.data(subticks, function(d, i) { | |
| return String(d.value); | |
| }); | |
| var subtickEnter = subtick.enter().insert("line", "g").attr("class", "tick minor").style("opacity", 1e-6); | |
| var subtickExit = d3.transition(subtick.exit()).style("opacity", 1e-6).remove(); | |
| var subtickUpdate = d3.transition(subtick).style("opacity", 1); | |
| // Major ticks. | |
| var tick = g.selectAll("g.major").data(ticks, function(d, i) { | |
| return String(d.value); | |
| }), | |
| tickEnter = tick.enter().insert("g", "path").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; | |
| // Domain. | |
| var path = g.selectAll(".domain").data([0]), | |
| pathEnter = path.enter().append("path").attr("class", "domain"), | |
| pathUpdate = d3.transition(path); | |
| // Stash a snapshot of the new scale, and retrieve the old snapshot. | |
| var scale1 = scale.copy(), | |
| scale0 = this.__chart__ || scale1; | |
| this.__chart__ = scale1; | |
| tickEnter.append("line").attr("class", "tick"); | |
| tickEnter.append("text").attr("class", "tick-text"); | |
| var lineEnter = tickEnter.select("line.tick"), | |
| lineUpdate = tickUpdate.select("line.tick"), | |
| text = tick.select("text.tick-text").text(function(d, i) { | |
| if (tickFormatExtended_ == null) | |
| return tickFormat(d.value); | |
| else | |
| return tickFormatExtended_(d, i); | |
| }), | |
| textEnter = tickEnter.select("text.tick-text"), | |
| textUpdate = tickUpdate.select("text.tick-text"); | |
| switch (orient) { | |
| case "bottom": { | |
| tickTransform = d3_svg_axisX; | |
| subtickEnter.attr("x2", 0).attr("y2", function(d, i) { | |
| return +tickMinorSize(d, i); | |
| }); | |
| subtickUpdate.attr("x2", 0).attr("y2", function(d, i) { | |
| return +tickMinorSize(d, i); | |
| }); | |
| lineEnter.attr("x2", 0).attr("y2", tickMajorSize); | |
| textEnter.attr("x", 0).attr("y", function(d, i) { | |
| return Math.max(+tickMajorSize(d, i), 0) + tickPadding; | |
| }); | |
| lineUpdate.attr("x2", 0).attr("y2", tickMajorSize); | |
| textUpdate.attr("x", 0).attr("y", function (d, i) { | |
| return Math.max(+tickMajorSize(d, i), 0) + tickPadding; | |
| }); | |
| text.attr("dy", ".71em").attr("text-anchor", "middle"); | |
| pathUpdate.attr("d", "M" + range[0] + "," + tickEndSize(range, 0) + "V0H" + range[1] + "V" + tickEndSize(range, 1)); | |
| break; | |
| } | |
| case "top": { | |
| tickTransform = d3_svg_axisX; | |
| subtickEnter.attr("y2", function(d, i) { | |
| return -tickMinorSize(d, i); | |
| }); | |
| subtickUpdate.attr("x2", 0).attr("y2", function(d, i) { | |
| return -tickMinorSize(d, i); | |
| }); | |
| lineEnter.attr("y2", function(d, i) { | |
| return -tickMajorSize(d, i); | |
| }); | |
| textEnter.attr("y", function(d, i) { | |
| return -(Math.max(+tickMajorSize(d, i), 0) + tickPadding); | |
| }); | |
| lineUpdate.attr("x2", 0).attr("y2", function(d, i) { | |
| return -tickMajorSize(d, i); | |
| }); | |
| textUpdate.attr("x", 0).attr("y", function(d, i) { | |
| return -(Math.max(+tickMajorSize(d, i), 0) + tickPadding); | |
| }); | |
| text.attr("dy", "0em").attr("text-anchor", "middle"); | |
| pathUpdate.attr("d", "M" + range[0] + "," + -tickEndSize(range, 0) + "V0H" + range[1] + "V" + -tickEndSize(range, 1)); | |
| break; | |
| } | |
| case "left": { | |
| tickTransform = d3_svg_axisY; | |
| subtickEnter.attr("x2", function(d, i) { | |
| return -tickMinorSize(d, i); | |
| }); | |
| subtickUpdate.attr("x2", function(d, i) { | |
| return -tickMinorSize(d, i); | |
| }).attr("y2", 0); | |
| lineEnter.attr("x2", function(d, i) { | |
| return -tickMajorSize(d, i); | |
| }); | |
| textEnter.attr("x", function(d, i) { | |
| return -(Math.max(+tickMajorSize(d, i), 0) + tickPadding); | |
| }); | |
| lineUpdate.attr("x2", function(d, i) { | |
| return -tickMajorSize(d, i); | |
| }).attr("y2", 0); | |
| textUpdate.attr("x", function(d, i) { | |
| return -(Math.max(+tickMajorSize(d, i), 0) + tickPadding); | |
| }).attr("y", 0); | |
| text.attr("dy", ".32em").attr("text-anchor", "end"); | |
| pathUpdate.attr("d", "M" + -tickEndSize(range, 0) + "," + range[0] + "H0V" + range[1] + "H" + -tickEndSize(range, 1)); | |
| break; | |
| } | |
| case "right": { | |
| tickTransform = d3_svg_axisY; | |
| subtickEnter.attr("x2", tickMinorSize); | |
| subtickUpdate.attr("x2", tickMinorSize).attr("y2", 0); | |
| lineEnter.attr("x2", tickMajorSize); | |
| textEnter.attr("x", function(d, i) { | |
| return Math.max(+tickMajorSize(d, i), 0) + tickPadding; | |
| }); | |
| lineUpdate.attr("x2", tickMajorSize).attr("y2", 0); | |
| textUpdate.attr("x", function(d, i) { | |
| return Math.max(+tickMajorSize(d, i), 0) + tickPadding; | |
| }).attr("y", 0); | |
| text.attr("dy", ".32em").attr("text-anchor", "start"); | |
| pathUpdate.attr("d", "M" + tickEndSize(range, 0) + "," + range[0] + "H0V" + range[1] + "H" + tickEndSize(range, 1)); | |
| break; | |
| } | |
| } | |
| // For quantitative scales: | |
| // - enter new ticks from the old scale | |
| // - exit old ticks to the new scale | |
| if (scale.ticks) { | |
| tickEnter.call(tickTransform, scale0); | |
| tickUpdate.call(tickTransform, scale1); | |
| tickExit.call(tickTransform, scale1); | |
| subtickEnter.call(tickTransform, scale0); | |
| subtickUpdate.call(tickTransform, scale1); | |
| subtickExit.call(tickTransform, scale1); | |
| } | |
| // For ordinal scales: | |
| // - any entering ticks are undefined in the old scale | |
| // - any exiting ticks are undefined in the new scale | |
| // Therefore, we only need to transition updating ticks. | |
| else { | |
| var dx = scale1.rangeBand() / 2, x = function(d) { return scale1(d) + dx; }; | |
| tickEnter.call(tickTransform, x); | |
| tickUpdate.call(tickTransform, x); | |
| } | |
| }); | |
| return false; | |
| } else { | |
| // when using d3.axis other than in a d3.selection.call(...); produce the ticks, etc. for custom work: | |
| return { | |
| ticks: ticks, | |
| subticks: subticks, | |
| range: range, // array[2] | |
| tickMajorSize: tickMajorSize, // functor(d, i) | |
| tickMinorSize: tickMinorSize, // functor(d, i) | |
| tickEndSize: tickEndSize, // functor(d, i) | |
| tickPadding: tickPadding, // Number | |
| tickFormat: tickFormat, // functor(d) | |
| tickFormatExtended: tickFormatExtended_ // functor(d, i) | |
| }; | |
| } | |
| } | |
| axis.scale = function(x) { | |
| if (!arguments.length) return scale; | |
| scale = x; | |
| return axis; | |
| }; | |
| axis.orient = function(x) { | |
| if (!arguments.length) return orient; | |
| orient = x; | |
| return axis; | |
| }; | |
| axis.ticks = function() { | |
| if (!arguments.length) return tickArguments_; | |
| tickArguments_ = arguments; | |
| return axis; | |
| }; | |
| axis.tickValues = function(x) { | |
| if (!arguments.length) return tickValues; | |
| tickValues = x; | |
| return axis; | |
| }; | |
| axis.tickFormat = function(x) { | |
| if (!arguments.length) return tickFormat_; | |
| tickFormat_ = x; | |
| return axis; | |
| }; | |
| axis.tickFormatEx = function(x) { | |
| if (!arguments.length) return tickFormatExtended_; | |
| tickFormatExtended_ = extended; | |
| return axis; | |
| }; | |
| axis.tickSize = function(major, minor, end) { | |
| var n = arguments.length; | |
| if (!n) return [tickMajorSize, tickMinorSize, tickEndSize]; | |
| tickMajorSize = d3_functor(major); | |
| tickMinorSize = n > 2 ? d3_functor(minor) : tickMajorSize; | |
| tickEndSize = d3_functor(arguments[n - 1]); | |
| return axis; | |
| }; | |
| axis.tickPadding = function(x) { | |
| if (!arguments.length) return tickPadding; | |
| tickPadding = +x; | |
| return axis; | |
| }; | |
| axis.tickSubdivide = function(x) { | |
| if (!arguments.length) return tickSubdivide; | |
| tickSubdivide = (x != null ? typeof x !== "function" ? d3_svg_axisTickSubDivideOneTick(+x) : x : null); | |
| return axis; | |
| }; | |
| axis.tickFilter = function(x) { | |
| if (!arguments.length) return tickFilter; | |
| tickFilter = (x != null ? d3_functor(x) : d3_functor(true)); | |
| return axis; | |
| }; | |
| return axis; | |
| }; | |
| function d3_svg_axisX(selection, x) { | |
| selection.attr("transform", function(d) { | |
| return "translate(" + x(d.value) + ",0)"; | |
| }); | |
| } | |
| function d3_svg_axisY(selection, y) { | |
| selection.attr("transform", function(d) { | |
| return "translate(0," + y(d.value) + ")"; | |
| }); | |
| } | |
| function d3_svg_axisSubdivide(scale, ticks, subdiv) { | |
| var subticks = []; | |
| if (subdiv && ticks.length > 1) { | |
| var extent = d3_scaleExtent(scale.domain()), | |
| i, | |
| n = ticks.length; | |
| for (i = 0; i <= n; i++) { | |
| subticks = subdiv(subticks, ticks, i, n, extent); | |
| } | |
| } | |
| return subticks; | |
| } | |
| // Return a function which produces an array of subtick objects for one tick interval: | |
| function d3_svg_axisTickSubDivideOneTick(modulus) { | |
| modulus++; | |
| return function(subticks, ticks, i, n, extent) { | |
| var t0, t1, delta, s, j, v; | |
| if (i == 0) { | |
| t0 = ticks[0]; | |
| t1 = ticks[1]; | |
| delta = (t1.value - t0.value) / modulus; | |
| for (j = modulus; j-- > 1; ) { | |
| v = t0.value - j * delta; | |
| if (v > extent[0]) { | |
| subticks.push({ | |
| value: v, | |
| index: -1, | |
| base: t0, | |
| subindex: -j, | |
| modulus: modulus, | |
| majors: ticks | |
| }); | |
| } | |
| } | |
| } else if (i == n) { | |
| t0 = ticks[n - 2]; | |
| t1 = ticks[n - 1]; | |
| delta = (t1.value - t0.value) / modulus; | |
| for (j = modulus; j-- > 1; ) { | |
| v = t1.value + j * delta; | |
| if (v < extent[1]) { | |
| subticks.push({ | |
| value: v, | |
| index: n - 1, | |
| base: t1, | |
| subindex: j, | |
| modulus: modulus, | |
| majors: ticks | |
| }); | |
| } | |
| } | |
| } else { | |
| t0 = ticks[i - 1]; | |
| t1 = ticks[i]; | |
| delta = (t1.value - t0.value) / modulus; | |
| for (j = modulus; j-- > 1; ) { | |
| v = t0.value + j * delta; | |
| if (v > extent[0]) { | |
| subticks.push({ | |
| value: v, | |
| index: i - 1, | |
| base: t0, | |
| subindex: j, | |
| modulus: modulus, | |
| majors: ticks | |
| }); | |
| } | |
| } | |
| } | |
| return subticks; | |
| }; | |
| } | |
| function d3_svg_axisMapTicks(v, i, ticks) { | |
| return { | |
| value: v, | |
| index: i | |
| }; | |
| }d3.svg.brush = function() { | |
| var event = d3_eventDispatch(brush, "brushstart", "brush", "brushend"), | |
| x = null, // x-scale, optional | |
| y = null, // y-scale, optional | |
| resizes = d3_svg_brushResizes[0], | |
| extent = [[0, 0], [0, 0]], // [x0, y0], [x1, y1], in pixels (integers) | |
| extentDomain; // the extent in data space, lazily created | |
| function brush(g) { | |
| g.each(function() { | |
| var g = d3.select(this), | |
| bg = g.selectAll(".background").data([0]), | |
| fg = g.selectAll(".extent").data([0]), | |
| tz = g.selectAll(".resize").data(resizes, String), | |
| e; | |
| // Prepare the brush container for events. | |
| g | |
| .style("pointer-events", "all") | |
| .on("mousedown.brush", brushstart) | |
| .on("touchstart.brush", brushstart); | |
| // An invisible, mouseable area for starting a new brush. | |
| bg.enter().append("rect") | |
| .attr("class", "background") | |
| .style("visibility", "hidden") | |
| .style("cursor", "crosshair"); | |
| // The visible brush extent; style this as you like! | |
| fg.enter().append("rect") | |
| .attr("class", "extent") | |
| .style("cursor", "move"); | |
| // More invisible rects for resizing the extent. | |
| tz.enter().append("g") | |
| .attr("class", function(d) { return "resize " + d; }) | |
| .style("cursor", function(d) { return d3_svg_brushCursor[d]; }) | |
| .append("rect") | |
| .attr("x", function(d) { return /[ew]$/.test(d) ? -3 : null; }) | |
| .attr("y", function(d) { return /^[ns]/.test(d) ? -3 : null; }) | |
| .attr("width", 6) | |
| .attr("height", 6) | |
| .style("visibility", "hidden"); | |
| // Show or hide the resizers. | |
| tz.style("display", brush.empty() ? "none" : null); | |
| // Remove any superfluous resizers. | |
| tz.exit().remove(); | |
| // Initialize the background to fill the defined range. | |
| // If the range isn't defined, you can post-process. | |
| if (x) { | |
| e = d3_scaleRange(x); | |
| bg.attr("x", e[0]).attr("width", e[1] - e[0]); | |
| redrawX(g); | |
| } | |
| if (y) { | |
| e = d3_scaleRange(y); | |
| bg.attr("y", e[0]).attr("height", e[1] - e[0]); | |
| redrawY(g); | |
| } | |
| redraw(g); | |
| }); | |
| } | |
| function redraw(g) { | |
| g.selectAll(".resize").attr("transform", function(d) { | |
| return "translate(" + extent[+/e$/.test(d)][0] + "," + extent[+/^s/.test(d)][1] + ")"; | |
| }); | |
| } | |
| function redrawX(g) { | |
| g.select(".extent").attr("x", extent[0][0]); | |
| g.selectAll(".extent,.n>rect,.s>rect").attr("width", extent[1][0] - extent[0][0]); | |
| } | |
| function redrawY(g) { | |
| g.select(".extent").attr("y", extent[0][1]); | |
| g.selectAll(".extent,.e>rect,.w>rect").attr("height", extent[1][1] - extent[0][1]); | |
| } | |
| function brushstart() { | |
| var target = this, | |
| eventTarget = d3.select(d3.event.target), | |
| event_ = event.of(target, arguments), | |
| g = d3.select(target), | |
| resizing = eventTarget.datum(), | |
| resizingX = !/^(n|s)$/.test(resizing) && x, | |
| resizingY = !/^(e|w)$/.test(resizing) && y, | |
| dragging = eventTarget.classed("extent"), | |
| center, | |
| origin = mouse(), | |
| offset; | |
| var w = d3.select(window) | |
| .on("mousemove.brush", brushmove) | |
| .on("mouseup.brush", brushend) | |
| .on("touchmove.brush", brushmove) | |
| .on("touchend.brush", brushend) | |
| .on("keydown.brush", keydown) | |
| .on("keyup.brush", keyup); | |
| // If the extent was clicked on, drag rather than brush; | |
| // store the point between the mouse and extent origin instead. | |
| if (dragging) { | |
| origin[0] = extent[0][0] - origin[0]; | |
| origin[1] = extent[0][1] - origin[1]; | |
| } | |
| // If a resizer was clicked on, record which side is to be resized. | |
| // Also, set the origin to the opposite side. | |
| else if (resizing) { | |
| var ex = +/w$/.test(resizing), | |
| ey = +/^n/.test(resizing); | |
| offset = [extent[1 - ex][0] - origin[0], extent[1 - ey][1] - origin[1]]; | |
| origin[0] = extent[ex][0]; | |
| origin[1] = extent[ey][1]; | |
| } | |
| // If the ALT key is down when starting a brush, the center is at the mouse. | |
| else if (d3.event.altKey) center = origin.slice(); | |
| // Propagate the active cursor to the body for the drag duration. | |
| g.style("pointer-events", "none").selectAll(".resize").style("display", null); | |
| g.selectAll(".background").style("pointer-events", "none"); // workaround for IE9 bug | |
| d3.select("body").style("cursor", eventTarget.style("cursor")); | |
| // Notify listeners. | |
| event_({type: "brushstart"}); | |
| brushmove(); | |
| d3_eventCancel(); | |
| function mouse() { | |
| var touches = d3.event.changedTouches; | |
| return touches ? d3.touches(target, touches)[0] : d3.mouse(target); | |
| } | |
| function keydown() { | |
| if (d3.event.keyCode == 32) { | |
| if (!dragging) { | |
| center = null; | |
| origin[0] -= extent[1][0]; | |
| origin[1] -= extent[1][1]; | |
| dragging = 2; | |
| } | |
| d3_eventCancel(); | |
| } | |
| } | |
| function keyup() { | |
| if (d3.event.keyCode == 32 && dragging == 2) { | |
| origin[0] += extent[1][0]; | |
| origin[1] += extent[1][1]; | |
| dragging = 0; | |
| d3_eventCancel(); | |
| } | |
| } | |
| function brushmove() { | |
| var point = mouse(), | |
| moved = false; | |
| // Preserve the offset for thick resizers. | |
| if (offset) { | |
| point[0] += offset[0]; | |
| point[1] += offset[1]; | |
| } | |
| if (!dragging) { | |
| // If needed, determine the center from the current extent. | |
| if (d3.event.altKey) { | |
| if (!center) center = [(extent[0][0] + extent[1][0]) / 2, (extent[0][1] + extent[1][1]) / 2]; | |
| // Update the origin, for when the ALT key is released. | |
| origin[0] = extent[+(point[0] < center[0])][0]; | |
| origin[1] = extent[+(point[1] < center[1])][1]; | |
| } | |
| // When the ALT key is released, we clear the center. | |
| else center = null; | |
| } | |
| // Update the brush extent for each dimension. | |
| if (resizingX && move1(point, x, 0)) { | |
| redrawX(g); | |
| moved = true; | |
| } | |
| if (resizingY && move1(point, y, 1)) { | |
| redrawY(g); | |
| moved = true; | |
| } | |
| // Final redraw and notify listeners. | |
| if (moved) { | |
| redraw(g); | |
| event_({type: "brush", mode: dragging ? "move" : "resize"}); | |
| } | |
| } | |
| function move1(point, scale, i) { | |
| var range = d3_scaleRange(scale), | |
| r0 = range[0], | |
| r1 = range[1], | |
| position = origin[i], | |
| size = extent[1][i] - extent[0][i], | |
| min, | |
| max; | |
| // When dragging, reduce the range by the extent size and position. | |
| if (dragging) { | |
| r0 -= position; | |
| r1 -= size + position; | |
| } | |
| // Clamp the point so that the extent fits within the range extent. | |
| min = Math.max(r0, Math.min(r1, point[i])); | |
| // Compute the new extent bounds. | |
| if (dragging) { | |
| max = (min += position) + size; | |
| } else { | |
| // If the ALT key is pressed, then preserve the center of the extent. | |
| if (center) position = Math.max(r0, Math.min(r1, 2 * center[i] - min)); | |
| // Compute the min and max of the position and point. | |
| if (position < min) { | |
| max = min; | |
| min = position; | |
| } else { | |
| max = position; | |
| } | |
| } | |
| // Update the stored bounds. | |
| if (extent[0][i] !== min || extent[1][i] !== max) { | |
| extentDomain = null; | |
| extent[0][i] = min; | |
| extent[1][i] = max; | |
| return true; | |
| } | |
| } | |
| function brushend() { | |
| brushmove(); | |
| // reset the cursor styles | |
| g.style("pointer-events", "all").selectAll(".resize").style("display", brush.empty() ? "none" : null); | |
| g.selectAll(".background").style("pointer-events", null); // workaround for IE9 bug | |
| d3.select("body").style("cursor", null); | |
| w .on("mousemove.brush", null) | |
| .on("mouseup.brush", null) | |
| .on("touchmove.brush", null) | |
| .on("touchend.brush", null) | |
| .on("keydown.brush", null) | |
| .on("keyup.brush", null); | |
| event_({type: "brushend"}); | |
| d3_eventCancel(); | |
| } | |
| } | |
| brush.x = function(z) { | |
| if (!arguments.length) return x; | |
| x = z; | |
| resizes = d3_svg_brushResizes[!x << 1 | !y]; // fore! | |
| return brush; | |
| }; | |
| brush.y = function(z) { | |
| if (!arguments.length) return y; | |
| y = z; | |
| resizes = d3_svg_brushResizes[!x << 1 | !y]; // fore! | |
| return brush; | |
| }; | |
| brush.extent = function(z) { | |
| var x0, x1, y0, y1, t; | |
| // Invert the pixel extent to data-space. | |
| if (!arguments.length) { | |
| z = extentDomain || extent; | |
| if (x) { | |
| x0 = z[0][0], x1 = z[1][0]; | |
| if (!extentDomain) { | |
| x0 = extent[0][0], x1 = extent[1][0]; | |
| if (x.invert) x0 = x.invert(x0), x1 = x.invert(x1); | |
| if (x1 < x0) t = x0, x0 = x1, x1 = t; | |
| } | |
| } | |
| if (y) { | |
| y0 = z[0][1], y1 = z[1][1]; | |
| if (!extentDomain) { | |
| y0 = extent[0][1], y1 = extent[1][1]; | |
| if (y.invert) y0 = y.invert(y0), y1 = y.invert(y1); | |
| if (y1 < y0) t = y0, y0 = y1, y1 = t; | |
| } | |
| } | |
| return x && y ? [[x0, y0], [x1, y1]] : x ? [x0, x1] : y && [y0, y1]; | |
| } | |
| // Scale the data-space extent to pixels. | |
| extentDomain = [[0, 0], [0, 0]]; | |
| if (x) { | |
| x0 = z[0], x1 = z[1]; | |
| if (y) x0 = x0[0], x1 = x1[0]; | |
| extentDomain[0][0] = x0, extentDomain[1][0] = x1; | |
| if (x.invert) x0 = x(x0), x1 = x(x1); | |
| if (x1 < x0) t = x0, x0 = x1, x1 = t; | |
| extent[0][0] = x0 | 0, extent[1][0] = x1 | 0; | |
| } | |
| if (y) { | |
| y0 = z[0], y1 = z[1]; | |
| if (x) y0 = y0[1], y1 = y1[1]; | |
| extentDomain[0][1] = y0, extentDomain[1][1] = y1; | |
| if (y.invert) y0 = y(y0), y1 = y(y1); | |
| if (y1 < y0) t = y0, y0 = y1, y1 = t; | |
| extent[0][1] = y0 | 0, extent[1][1] = y1 | 0; | |
| } | |
| return brush; | |
| }; | |
| brush.clear = function() { | |
| extentDomain = null; | |
| extent[0][0] = | |
| extent[0][1] = | |
| extent[1][0] = | |
| extent[1][1] = 0; | |
| return brush; | |
| }; | |
| brush.empty = function() { | |
| return (x && extent[0][0] === extent[1][0]) | |
| || (y && extent[0][1] === extent[1][1]); | |
| }; | |
| return d3.rebind(brush, event, "on"); | |
| }; | |
| var d3_svg_brushCursor = { | |
| n: "ns-resize", | |
| e: "ew-resize", | |
| s: "ns-resize", | |
| w: "ew-resize", | |
| nw: "nwse-resize", | |
| ne: "nesw-resize", | |
| se: "nwse-resize", | |
| sw: "nesw-resize" | |
| }; | |
| var d3_svg_brushResizes = [ | |
| ["n", "e", "s", "w", "nw", "ne", "se", "sw"], | |
| ["e", "w"], | |
| ["n", "s"], | |
| [] | |
| ]; | |
| d3.behavior = {}; | |
| d3.behavior.drag = function() { | |
| var event = d3_eventDispatch(drag, "drag", "dragstart", "dragend"), | |
| origin = null; | |
| function drag() { | |
| this.on("mousedown.drag", mousedown) | |
| .on("touchstart.drag", mousedown); | |
| } | |
| function mousedown() { | |
| var target = this, | |
| event_ = event.of(target, arguments), | |
| eventTarget = d3.event.target, | |
| touchId = d3.event.touches && d3.event.changedTouches[0].identifier, | |
| offset, | |
| origin_ = point(), | |
| moved = 0; | |
| var w = d3.select(window) | |
| .on(touchId ? "touchmove.drag-" + touchId : "mousemove.drag", dragmove) | |
| .on(touchId ? "touchend.drag-" + touchId : "mouseup.drag", dragend, true); | |
| if (origin) { | |
| offset = origin.apply(target, arguments); | |
| offset = [offset.x - origin_[0], offset.y - origin_[1]]; | |
| } else { | |
| offset = [0, 0]; | |
| } | |
| // Only cancel mousedown; touchstart is needed for draggable links. | |
| if (!touchId) d3_eventCancel(); | |
| event_({type: "dragstart"}); | |
| function point() { | |
| var p = target.parentNode; | |
| return touchId | |
| ? d3.touches(p).filter(function(p) { return p.identifier === touchId; })[0] | |
| : d3.mouse(p); | |
| } | |
| function dragmove() { | |
| if (!target.parentNode) return dragend(); // target removed from DOM | |
| var p = point(), | |
| dx = p[0] - origin_[0], | |
| dy = p[1] - origin_[1]; | |
| moved |= dx | dy; | |
| origin_ = p; | |
| d3_eventCancel(); | |
| event_({type: "drag", x: p[0] + offset[0], y: p[1] + offset[1], dx: dx, dy: dy}); | |
| } | |
| function dragend() { | |
| event_({type: "dragend"}); | |
| // if moved, prevent the mouseup (and possibly click) from propagating | |
| if (moved) { | |
| d3_eventCancel(); | |
| if (d3.event.target === eventTarget) w.on("click.drag", click, true); | |
| } | |
| w .on(touchId ? "touchmove.drag-" + touchId : "mousemove.drag", null) | |
| .on(touchId ? "touchend.drag-" + touchId : "mouseup.drag", null); | |
| } | |
| // prevent the subsequent click from propagating (e.g., for anchors) | |
| function click() { | |
| d3_eventCancel(); | |
| w.on("click.drag", null); | |
| } | |
| } | |
| drag.origin = function(x) { | |
| if (!arguments.length) return origin; | |
| origin = x; | |
| return drag; | |
| }; | |
| return d3.rebind(drag, event, "on"); | |
| }; | |
| d3.behavior.zoom = function() { | |
| var translate = [0, 0], | |
| translate0, // translate when we started zooming (to avoid drift) | |
| scale = 1, | |
| scale0, // scale when we started touching | |
| scaleExtent = d3_behavior_zoomInfinity, | |
| event = d3_eventDispatch(zoom, "zoom"), | |
| x0, | |
| x1, | |
| y0, | |
| y1, | |
| touchtime; // time of last touchstart (to detect double-tap) | |
| function zoom() { | |
| this | |
| .on("mousedown.zoom", mousedown) | |
| .on("mousewheel.zoom", mousewheel) | |
| .on("mousemove.zoom", mousemove) | |
| .on("DOMMouseScroll.zoom", mousewheel) | |
| .on("dblclick.zoom", dblclick) | |
| .on("touchstart.zoom", touchstart) | |
| .on("touchmove.zoom", touchmove) | |
| .on("touchend.zoom", touchstart); | |
| } | |
| zoom.translate = function(x) { | |
| if (!arguments.length) return translate; | |
| translate = x.map(Number); | |
| return zoom; | |
| }; | |
| zoom.scale = function(x) { | |
| if (!arguments.length) return scale; | |
| scale = +x; | |
| return zoom; | |
| }; | |
| zoom.scaleExtent = function(x) { | |
| if (!arguments.length) return scaleExtent; | |
| scaleExtent = x == null ? d3_behavior_zoomInfinity : x.map(Number); | |
| return zoom; | |
| }; | |
| zoom.x = function(z) { | |
| if (!arguments.length) return x1; | |
| x1 = z; | |
| x0 = z.copy(); | |
| return zoom; | |
| }; | |
| zoom.y = function(z) { | |
| if (!arguments.length) return y1; | |
| y1 = z; | |
| y0 = z.copy(); | |
| return zoom; | |
| }; | |
| function location(p) { | |
| return [(p[0] - translate[0]) / scale, (p[1] - translate[1]) / scale]; | |
| } | |
| function point(l) { | |
| return [l[0] * scale + translate[0], l[1] * scale + translate[1]]; | |
| } | |
| function scaleTo(s) { | |
| scale = Math.max(scaleExtent[0], Math.min(scaleExtent[1], s)); | |
| } | |
| function translateTo(p, l) { | |
| l = point(l); | |
| translate[0] += p[0] - l[0]; | |
| translate[1] += p[1] - l[1]; | |
| } | |
| function dispatch(event) { | |
| if (x1) x1.domain(x0.range().map(function(x) { return (x - translate[0]) / scale; }).map(x0.invert)); | |
| if (y1) y1.domain(y0.range().map(function(y) { return (y - translate[1]) / scale; }).map(y0.invert)); | |
| d3.event.preventDefault(); | |
| event({type: "zoom", scale: scale, translate: translate}); | |
| } | |
| function mousedown() { | |
| var target = this, | |
| event_ = event.of(target, arguments), | |
| eventTarget = d3.event.target, | |
| moved = 0, | |
| w = d3.select(window).on("mousemove.zoom", mousemove).on("mouseup.zoom", mouseup), | |
| l = location(d3.mouse(target)); | |
| window.focus(); | |
| d3_eventCancel(); | |
| function mousemove() { | |
| moved = 1; | |
| translateTo(d3.mouse(target), l); | |
| dispatch(event_); | |
| } | |
| function mouseup() { | |
| if (moved) d3_eventCancel(); | |
| w.on("mousemove.zoom", null).on("mouseup.zoom", null); | |
| if (moved && d3.event.target === eventTarget) w.on("click.zoom", click, true); | |
| } | |
| function click() { | |
| d3_eventCancel(); | |
| w.on("click.zoom", null); | |
| } | |
| } | |
| function mousewheel() { | |
| if (!translate0) translate0 = location(d3.mouse(this)); | |
| scaleTo(Math.pow(2, d3_behavior_zoomDelta() * .002) * scale); | |
| translateTo(d3.mouse(this), translate0); | |
| dispatch(event.of(this, arguments)); | |
| } | |
| function mousemove() { | |
| translate0 = null; | |
| } | |
| function dblclick() { | |
| var p = d3.mouse(this), l = location(p); | |
| scaleTo(d3.event.shiftKey ? scale / 2 : scale * 2); | |
| translateTo(p, l); | |
| dispatch(event.of(this, arguments)); | |
| } | |
| function touchstart() { | |
| var touches = d3.touches(this), | |
| now = Date.now(); | |
| scale0 = scale; | |
| translate0 = {}; | |
| touches.forEach(function(t) { translate0[t.identifier] = location(t); }); | |
| d3_eventCancel(); | |
| if (touches.length === 1) { | |
| if (now - touchtime < 500) { // dbltap | |
| var p = touches[0], l = location(touches[0]); | |
| scaleTo(scale * 2); | |
| translateTo(p, l); | |
| dispatch(event.of(this, arguments)); | |
| } | |
| touchtime = now; | |
| } | |
| } | |
| function touchmove() { | |
| var touches = d3.touches(this), | |
| p0 = touches[0], | |
| l0 = translate0[p0.identifier]; | |
| if (p1 = touches[1]) { | |
| var p1, l1 = translate0[p1.identifier]; | |
| p0 = [(p0[0] + p1[0]) / 2, (p0[1] + p1[1]) / 2]; | |
| l0 = [(l0[0] + l1[0]) / 2, (l0[1] + l1[1]) / 2]; | |
| scaleTo(d3.event.scale * scale0); | |
| } | |
| translateTo(p0, l0); | |
| touchtime = null; | |
| dispatch(event.of(this, arguments)); | |
| } | |
| return d3.rebind(zoom, event, "on"); | |
| }; | |
| var d3_behavior_zoomDiv, // for interpreting mousewheel events | |
| d3_behavior_zoomInfinity = [0, Infinity]; // default scale extent | |
| function d3_behavior_zoomDelta() { | |
| // mousewheel events are totally broken! | |
| // https://bugs.webkit.org/show_bug.cgi?id=40441 | |
| // not only that, but Chrome and Safari differ in re. to acceleration! | |
| if (!d3_behavior_zoomDiv) { | |
| d3_behavior_zoomDiv = d3.select("body").append("div") | |
| .style("visibility", "hidden") | |
| .style("top", 0) | |
| .style("height", 0) | |
| .style("width", 0) | |
| .style("overflow-y", "scroll") | |
| .append("div") | |
| .style("height", "2000px") | |
| .node().parentNode; | |
| } | |
| var e = d3.event, delta; | |
| try { | |
| d3_behavior_zoomDiv.scrollTop = 1000; | |
| d3_behavior_zoomDiv.dispatchEvent(e); | |
| delta = 1000 - d3_behavior_zoomDiv.scrollTop; | |
| } catch (error) { | |
| delta = e.wheelDelta || (-e.detail * 5); | |
| } | |
| return delta; | |
| } | |
| d3.layout = {}; | |
| // Implements hierarchical edge bundling using Holten's algorithm. For each | |
| // input link, a path is computed that travels through the tree, up the parent | |
| // hierarchy to the least common ancestor, and then back down to the destination | |
| // node. Each path is simply an array of nodes. | |
| d3.layout.bundle = function() { | |
| return function(links) { | |
| var paths = [], | |
| i = -1, | |
| n = links.length; | |
| while (++i < n) paths.push(d3_layout_bundlePath(links[i])); | |
| return paths; | |
| }; | |
| }; | |
| function d3_layout_bundlePath(link) { | |
| var start = link.source, | |
| end = link.target, | |
| lca = d3_layout_bundleLeastCommonAncestor(start, end), | |
| points = [start]; | |
| while (start !== lca) { | |
| start = start.parent; | |
| points.push(start); | |
| } | |
| var k = points.length; | |
| while (end !== lca) { | |
| points.splice(k, 0, end); | |
| end = end.parent; | |
| } | |
| return points; | |
| } | |
| function d3_layout_bundleAncestors(node) { | |
| var ancestors = [], | |
| parent = node.parent; | |
| while (parent != null) { | |
| ancestors.push(node); | |
| node = parent; | |
| parent = parent.parent; | |
| } | |
| ancestors.push(node); | |
| return ancestors; | |
| } | |
| function d3_layout_bundleLeastCommonAncestor(a, b) { | |
| if (a === b) return a; | |
| var aNodes = d3_layout_bundleAncestors(a), | |
| bNodes = d3_layout_bundleAncestors(b), | |
| aNode = aNodes.pop(), | |
| bNode = bNodes.pop(), | |
| sharedNode = null; | |
| while (aNode === bNode) { | |
| sharedNode = aNode; | |
| aNode = aNodes.pop(); | |
| bNode = bNodes.pop(); | |
| } | |
| return sharedNode; | |
| } | |
| d3.layout.chord = function() { | |
| var chord = {}, | |
| chords, | |
| groups, | |
| matrix, | |
| n, | |
| padding = 0, | |
| sortGroups, | |
| sortSubgroups, | |
| sortChords; | |
| function relayout() { | |
| var subgroups = {}, | |
| groupSums = [], | |
| groupIndex = d3.range(n), | |
| subgroupIndex = [], | |
| k, | |
| x, | |
| x0, | |
| i, | |
| j; | |
| chords = []; | |
| groups = []; | |
| // Compute the sum. | |
| k = 0, i = -1; while (++i < n) { | |
| x = 0, j = -1; while (++j < n) { | |
| x += matrix[i][j]; | |
| } | |
| groupSums.push(x); | |
| subgroupIndex.push(d3.range(n)); | |
| k += x; | |
| } | |
| // Sort groups… | |
| if (sortGroups) { | |
| groupIndex.sort(function(a, b) { | |
| return sortGroups(groupSums[a], groupSums[b]); | |
| }); | |
| } | |
| // Sort subgroups… | |
| if (sortSubgroups) { | |
| subgroupIndex.forEach(function(d, i) { | |
| d.sort(function(a, b) { | |
| return sortSubgroups(matrix[i][a], matrix[i][b]); | |
| }); | |
| }); | |
| } | |
| // Convert the sum to scaling factor for [0, 2pi]. | |
| // TODO Allow start and end angle to be specified. | |
| // TODO Allow padding to be specified as percentage? | |
| k = (2 * Math.PI - padding * n) / k; | |
| // Compute the start and end angle for each group and subgroup. | |
| // Note: Opera has a bug reordering object literal properties! | |
| x = 0, i = -1; while (++i < n) { | |
| x0 = x, j = -1; while (++j < n) { | |
| var di = groupIndex[i], | |
| dj = subgroupIndex[di][j], | |
| v = matrix[di][dj], | |
| a0 = x, | |
| a1 = x += v * k; | |
| subgroups[di + "-" + dj] = { | |
| index: di, | |
| subindex: dj, | |
| startAngle: a0, | |
| endAngle: a1, | |
| value: v | |
| }; | |
| } | |
| groups[di] = { | |
| index: di, | |
| startAngle: x0, | |
| endAngle: x, | |
| value: (x - x0) / k | |
| }; | |
| x += padding; | |
| } | |
| // Generate chords for each (non-empty) subgroup-subgroup link. | |
| i = -1; while (++i < n) { | |
| j = i - 1; while (++j < n) { | |
| var source = subgroups[i + "-" + j], | |
| target = subgroups[j + "-" + i]; | |
| if (source.value || target.value) { | |
| chords.push(source.value < target.value | |
| ? {source: target, target: source} | |
| : {source: source, target: target}); | |
| } | |
| } | |
| } | |
| if (sortChords) resort(); | |
| } | |
| function resort() { | |
| chords.sort(function(a, b) { | |
| return sortChords( | |
| (a.source.value + a.target.value) / 2, | |
| (b.source.value + b.target.value) / 2); | |
| }); | |
| } | |
| chord.matrix = function(x) { | |
| if (!arguments.length) return matrix; | |
| n = (matrix = x) && matrix.length; | |
| chords = groups = null; | |
| return chord; | |
| }; | |
| chord.padding = function(x) { | |
| if (!arguments.length) return padding; | |
| padding = x; | |
| chords = groups = null; | |
| return chord; | |
| }; | |
| chord.sortGroups = function(x) { | |
| if (!arguments.length) return sortGroups; | |
| sortGroups = x; | |
| chords = groups = null; | |
| return chord; | |
| }; | |
| chord.sortSubgroups = function(x) { | |
| if (!arguments.length) return sortSubgroups; | |
| sortSubgroups = x; | |
| chords = null; | |
| return chord; | |
| }; | |
| chord.sortChords = function(x) { | |
| if (!arguments.length) return sortChords; | |
| sortChords = x; | |
| if (chords) resort(); | |
| return chord; | |
| }; | |
| chord.chords = function() { | |
| if (!chords) relayout(); | |
| return chords; | |
| }; | |
| chord.groups = function() { | |
| if (!groups) relayout(); | |
| return groups; | |
| }; | |
| return chord; | |
| }; | |
| // A rudimentary force layout using Gauss-Seidel. | |
| d3.layout.force = function() { | |
| var force = {}, | |
| event = d3.dispatch("start", "tick", "end"), | |
| size = [1, 1], | |
| drag, | |
| alpha, | |
| friction = d3_functor(.9), | |
| linkDistance = d3_layout_forceLinkDistance, | |
| linkStrength = d3_layout_forceLinkStrength, | |
| charge = d3_functor(-30), | |
| gravity = .1, | |
| theta = d3_functor(.8), | |
| interval, | |
| nodes = [], | |
| links = [], | |
| distances, | |
| strengths, | |
| epsilon = 0.1, // minimal distance-squared for which the approximation holds; any smaller distance is assumed to be this large to prevent instable approximations | |
| charges; | |
| function repulse(node, i) { | |
| return function(quad, x1, y1, x2, y2) { | |
| if (quad.point !== node) { | |
| var dx = quad.cx - node.x, | |
| dy = quad.cy - node.y, | |
| l = dx * dx + dy * dy, | |
| dn = 1 / Math.max(epsilon, l), | |
| k = quad.charge * dn, | |
| th = theta(node, i, quad, l, x1, x2, k); | |
| /* | |
| Based on the Barnes-Hut criterion. | |
| http://www.amara.com/papers/nbody.html#tcu | |
| Uses ideas from A Hierarchical O(N) Force Calculation Algorithm, Dehnen, 2007: | |
| http://physics.ucsd.edu/students/courses/winter2008/physics141/lecture16/2002JCP...179...27D.pdf | |
| to produce an improved acceptance criterion: Similar to the paper we not only | |
| collect the center of mass per quad but also the maximum distance of any | |
| node from that derived center of mass by collecting the bounding box; | |
| by comparing this measure with the distance of the quad's center of mass to our | |
| node and apply a decision threshold based on a (possibly) mass-dependent theta, | |
| we can tweak the accuracy of our approximation more accurately for fields with | |
| a very uneven charge distribution. | |
| The basic Barnes-Hut criterion is purely distance based, while we use a criterion | |
| which is similar to the potential-based criterion discussed in the latter paper: | |
| when the influence of the collective force over the given distance is relatively | |
| small, we do accept the consolidated quad data; otherwise we need process the | |
| child nodes. | |
| */ | |
| if (l * k * k < th * th) { | |
| k *= alpha; | |
| node.px -= dx * k; | |
| node.py -= dy * k; | |
| return true; | |
| } | |
| if (quad.point && isFinite(dn)) { | |
| k = quad.pointCharge * alpha * dn; | |
| node.px -= dx * k; | |
| node.py -= dy * k; | |
| } | |
| } | |
| return false; | |
| //return !quad.charge; -- very dangerous criterion to stop tree traversal as | |
| // accumulated force may be zero, but close-by so the | |
| // accumulate must not be used. | |
| // Another issue is when the node itself coincides | |
| // with others: those won't be visited if the sum of | |
| // the charges, including 'node' itself, produce a zero sum. | |
| }; | |
| } | |
| force.tick = function() { | |
| // simulated annealing, basically | |
| if ((alpha *= .99) < .005) { | |
| event.end({type: "end", alpha: alpha = 0}); | |
| return true; | |
| } | |
| var n = nodes.length, | |
| m = links.length, | |
| q, | |
| f, | |
| i, // current index | |
| o, // current object | |
| s, // current source | |
| t, // current target | |
| l, // current distance | |
| k, // current force | |
| x, // x-distance | |
| y; // y-distance | |
| // gauss-seidel relaxation for links | |
| for (i = 0; i < m; ++i) { | |
| o = links[i]; | |
| s = o.source; | |
| t = o.target; | |
| x = t.x - s.x; | |
| y = t.y - s.y; | |
| if (l = (x * x + y * y)) { | |
| l = alpha * strengths[i] * ((l = Math.sqrt(l)) - distances[i]) / l; | |
| x *= l; | |
| y *= l; | |
| t.x -= x * (k = s.weight / (t.weight + s.weight)); | |
| t.y -= y * k; | |
| s.x += x * (k = 1 - k); | |
| s.y += y * k; | |
| } | |
| } | |
| // apply gravity forces | |
| if (k = alpha * gravity) { | |
| x = size[0] / 2; | |
| y = size[1] / 2; | |
| i = -1; while (++i < n) { | |
| o = nodes[i]; | |
| o.x += (x - o.x) * k; | |
| o.y += (y - o.y) * k; | |
| } | |
| } | |
| // compute quadtree center of mass and apply charge forces | |
| f = 0; | |
| q = d3.geom.quadtree(nodes); | |
| // recalculate charges on every tick if need be: | |
| charges = []; | |
| for (i = 0; i < n; ++i) { | |
| charges[i] = k = +charge.call(this, nodes[i], i, q); | |
| f += Math.abs(k); | |
| } | |
| if (f != 0) { | |
| d3_layout_forceAccumulate(q, alpha, charges); | |
| i = -1; while (++i < n) { | |
| if (!(o = nodes[i]).fixed) { | |
| q.visit(repulse(o, i)); | |
| } | |
| } | |
| } | |
| // position verlet integration | |
| i = -1; while (++i < n) { | |
| o = nodes[i]; | |
| if (o.fixed) { | |
| o.x = o.px; | |
| o.y = o.py; | |
| } else { | |
| f = friction.call(this, o, i); | |
| o.x -= (o.px - (o.px = o.x)) * f; | |
| o.y -= (o.py - (o.py = o.y)) * f; | |
| } | |
| } | |
| event.tick({type: "tick", alpha: alpha, quadtree: q}); | |
| }; | |
| force.nodes = function(x) { | |
| if (!arguments.length) return nodes; | |
| nodes = x; | |
| return force; | |
| }; | |
| force.links = function(x) { | |
| if (!arguments.length) return links; | |
| links = x; | |
| return force; | |
| }; | |
| force.size = function(x) { | |
| if (!arguments.length) return size; | |
| size = x; | |
| return force; | |
| }; | |
| force.linkDistance = function(x) { | |
| if (!arguments.length) return linkDistance; | |
| linkDistance = d3_functor(x); | |
| return force; | |
| }; | |
| // For backwards-compatibility. | |
| force.distance = force.linkDistance; | |
| force.linkStrength = function(x) { | |
| if (!arguments.length) return linkStrength; | |
| linkStrength = d3_functor(x); | |
| return force; | |
| }; | |
| force.friction = function(x) { | |
| if (!arguments.length) return friction; | |
| friction = d3_functor(x); | |
| return force; | |
| }; | |
| force.charge = function(x) { | |
| if (!arguments.length) return charge; | |
| charge = d3_functor(x); | |
| return force; | |
| }; | |
| force.gravity = function(x) { | |
| if (!arguments.length) return gravity; | |
| gravity = +x; | |
| return force; | |
| }; | |
| force.theta = function(x) { | |
| if (!arguments.length) return theta; | |
| theta = +x; | |
| return force; | |
| }; | |
| force.alpha = function(x) { | |
| if (!arguments.length) return alpha; | |
| if (alpha) { // if we're already running | |
| if (x > 0) alpha = x; // we might keep it hot | |
| else alpha = 0; // or, next tick will dispatch "end" | |
| } else if (x > 0) { // otherwise, fire it up! | |
| event.start({type: "start", alpha: alpha = x}); | |
| d3.timer(force.tick); | |
| } | |
| return force; | |
| }; | |
| force.start = function() { | |
| var i, | |
| j, | |
| n = nodes.length, | |
| m = links.length, | |
| w = size[0], | |
| h = size[1], | |
| neighbors, | |
| o; | |
| for (i = 0; i < n; ++i) { | |
| (o = nodes[i]).index = i; | |
| o.weight = 0; | |
| } | |
| distances = []; | |
| strengths = []; | |
| for (i = 0; i < m; ++i) { | |
| o = links[i]; | |
| if (typeof o.source == "number") o.source = nodes[o.source]; | |
| if (typeof o.target == "number") o.target = nodes[o.target]; | |
| distances[i] = linkDistance.call(this, o, i); | |
| strengths[i] = linkStrength.call(this, o, i); | |
| ++o.source.weight; | |
| ++o.target.weight; | |
| } | |
| for (i = 0; i < n; ++i) { | |
| o = nodes[i]; | |
| if (isNaN(o.x)) o.x = position("x", w, i); | |
| if (isNaN(o.y)) o.y = position("y", h, i); | |
| if (isNaN(o.px)) o.px = o.x; | |
| if (isNaN(o.py)) o.py = o.y; | |
| } | |
| charges = []; | |
| for (i = 0; i < n; ++i) { | |
| charges[i] = +charge.call(this, nodes[i], i); | |
| } | |
| // initialize node position based on first neighbor | |
| function position(dimension, size, i) { | |
| var my_neighbors = neighbor(i), | |
| j = -1, | |
| m = my_neighbors.length, | |
| x; | |
| while (++j < m) if (!isNaN(x = my_neighbors[j][dimension])) return x; | |
| return Math.random() * size; | |
| } | |
| // initialize neighbors lazily | |
| function neighbor(i) { | |
| if (!neighbors) { | |
| var j; | |
| neighbors = []; | |
| for (j = 0; j < n; ++j) { | |
| neighbors[j] = []; | |
| } | |
| for (j = 0; j < m; ++j) { | |
| var o = links[j]; | |
| neighbors[o.source.index].push(o.target); | |
| neighbors[o.target.index].push(o.source); | |
| } | |
| } | |
| return neighbors[i]; | |
| } | |
| return force.resume(); | |
| }; | |
| force.resume = function() { | |
| return force.alpha(.1); | |
| }; | |
| force.stop = function() { | |
| return force.alpha(0); | |
| }; | |
| // use `node.call(force.drag)` to make nodes draggable | |
| force.drag = function() { | |
| if (!arguments.length) return drag; | |
| if (!drag) drag = d3.behavior.drag() | |
| .origin(d3_identity) | |
| .on("dragstart.force", d3_layout_forceDragstart) | |
| .on("drag.force", dragmove) | |
| .on("dragend.force", d3_layout_forceDragend); | |
| this.on("mouseover.force", d3_layout_forceMouseover) | |
| .on("mouseout.force", d3_layout_forceMouseout) | |
| .call(drag); | |
| }; | |
| function dragmove(d) { | |
| d.px = d3.event.x; | |
| d.py = d3.event.y; | |
| force.resume(); // restart annealing | |
| } | |
| return d3.rebind(force, event, "on"); | |
| }; | |
| // The fixed property has three bits: | |
| // Bit 1 can be set externally (e.g., d.fixed = true) and show persist. | |
| // Bit 2 stores the dragging state, from mousedown to mouseup. | |
| // Bit 3 stores the hover state, from mouseover to mouseout. | |
| // Dragend is a special case: it also clears the hover state. | |
| function d3_layout_forceDragstart(d) { | |
| d.fixed |= 2; // set bit 2 | |
| } | |
| function d3_layout_forceDragend(d) { | |
| d.fixed &= ~6; // unset bits 2 and 3 | |
| } | |
| function d3_layout_forceMouseover(d) { | |
| d.fixed |= 4; // set bit 3 | |
| } | |
| function d3_layout_forceMouseout(d) { | |
| d.fixed &= ~4; // unset bit 3 | |
| } | |
| function d3_layout_forceAccumulate(quad, alpha, charges) { | |
| var cx = 0, | |
| cy = 0; | |
| quad.charge = 0; | |
| if (!quad.leaf) { | |
| var nodes = quad.nodes, | |
| n = nodes.length, | |
| i = -1, | |
| c; | |
| while (++i < n) { | |
| c = nodes[i]; | |
| if (c == null) continue; | |
| d3_layout_forceAccumulate(c, alpha, charges); | |
| quad.charge += c.charge; | |
| cx += c.charge * c.cx; | |
| cy += c.charge * c.cy; | |
| } | |
| } | |
| if (quad.point) { | |
| // jitter internal nodes that are coincident | |
| if (!quad.leaf) { | |
| quad.point.x += Math.random() - .5; | |
| quad.point.y += Math.random() - .5; | |
| } | |
| var k = charges[quad.point.index]; | |
| quad.charge += quad.pointCharge = k; | |
| cx += k * quad.point.x; | |
| cy += k * quad.point.y; | |
| } | |
| quad.cx = cx / quad.charge; | |
| quad.cy = cy / quad.charge; | |
| } | |
| function d3_layout_forceLinkDistance(link) { | |
| return 20; | |
| } | |
| function d3_layout_forceLinkStrength(link) { | |
| return 1; | |
| } | |
| d3.layout.partition = function() { | |
| var hierarchy = d3.layout.hierarchy(), | |
| size = [1, 1]; // width, height | |
| function position(node, x, dx, dy) { | |
| var children = node.children; | |
| node.x = x; | |
| node.y = node.depth * dy; | |
| node.dx = dx; | |
| node.dy = dy; | |
| if (children && (n = children.length)) { | |
| var i = -1, | |
| n, | |
| c, | |
| d; | |
| dx = node.value ? dx / node.value : 0; | |
| while (++i < n) { | |
| position(c = children[i], x, d = c.value * dx, dy); | |
| x += d; | |
| } | |
| } | |
| } | |
| function depth(node) { | |
| var children = node.children, | |
| d = 0; | |
| if (children && (n = children.length)) { | |
| var i = -1, | |
| n; | |
| while (++i < n) d = Math.max(d, depth(children[i])); | |
| } | |
| return 1 + d; | |
| } | |
| function partition(d, i) { | |
| var nodes = hierarchy.call(this, d, i); | |
| position(nodes[0], 0, size[0], size[1] / depth(nodes[0])); | |
| return nodes; | |
| } | |
| partition.size = function(x) { | |
| if (!arguments.length) return size; | |
| size = x; | |
| return partition; | |
| }; | |
| return d3_layout_hierarchyRebind(partition, hierarchy); | |
| }; | |
| d3.layout.pie = function() { | |
| var value = Number, | |
| sort = d3_layout_pieSortByValue, | |
| startAngle = 0, | |
| endAngle = 2 * Math.PI; | |
| function pie(data, i) { | |
| // Compute the numeric values for each data element. | |
| var values = data.map(function(d, i) { return +value.call(pie, d, i); }); | |
| // Compute the start angle. | |
| var a = +(typeof startAngle === "function" | |
| ? startAngle.apply(this, arguments) | |
| : startAngle); | |
| // Compute the angular scale factor: from value to radians. | |
| var k = ((typeof endAngle === "function" | |
| ? endAngle.apply(this, arguments) | |
| : endAngle) - startAngle) | |
| / d3.sum(values); | |
| // Optionally sort the data. | |
| var index = d3.range(data.length); | |
| if (sort != null) index.sort(sort === d3_layout_pieSortByValue | |
| ? function(i, j) { return values[j] - values[i]; } | |
| : function(i, j) { return sort(data[i], data[j]); }); | |
| // Compute the arcs! | |
| // They are stored in the original data's order. | |
| var arcs = []; | |
| index.forEach(function(i) { | |
| var d; | |
| arcs[i] = { | |
| data: data[i], | |
| value: d = values[i], | |
| startAngle: a, | |
| endAngle: a += d * k | |
| }; | |
| }); | |
| return arcs; | |
| } | |
| /** | |
| * Specifies the value function *x*, which returns a nonnegative numeric value | |
| * for each datum. The default value function is `Number`. The value function | |
| * is passed two arguments: the current datum and the current index. | |
| */ | |
| pie.value = function(x) { | |
| if (!arguments.length) return value; | |
| value = x; | |
| return pie; | |
| }; | |
| /** | |
| * Specifies a sort comparison operator *x*. The comparator is passed two data | |
| * elements from the data array, a and b; it returns a negative value if a is | |
| * less than b, a positive value if a is greater than b, and zero if a equals | |
| * b. | |
| */ | |
| pie.sort = function(x) { | |
| if (!arguments.length) return sort; | |
| sort = x; | |
| return pie; | |
| }; | |
| /** | |
| * Specifies the overall start angle of the pie chart. Defaults to 0. The | |
| * start angle can be specified either as a constant or as a function; in the | |
| * case of a function, it is evaluated once per array (as opposed to per | |
| * element). | |
| */ | |
| pie.startAngle = function(x) { | |
| if (!arguments.length) return startAngle; | |
| startAngle = x; | |
| return pie; | |
| }; | |
| /** | |
| * Specifies the overall end angle of the pie chart. Defaults to 2π. The | |
| * end angle can be specified either as a constant or as a function; in the | |
| * case of a function, it is evaluated once per array (as opposed to per | |
| * element). | |
| */ | |
| pie.endAngle = function(x) { | |
| if (!arguments.length) return endAngle; | |
| endAngle = x; | |
| return pie; | |
| }; | |
| return pie; | |
| }; | |
| var d3_layout_pieSortByValue = {}; | |
| // data is two-dimensional array of x,y; we populate y0 | |
| d3.layout.stack = function() { | |
| var values = d3_identity, | |
| order = d3_layout_stackOrderDefault, | |
| offset = d3_layout_stackOffsetZero, | |
| out = d3_layout_stackOut, | |
| x = d3_layout_stackX, | |
| y = d3_layout_stackY; | |
| function stack(data, index) { | |
| // Convert series to canonical two-dimensional representation. | |
| var series = data.map(function(d, i) { | |
| return values.call(stack, d, i); | |
| }); | |
| // Convert each series to canonical [[x,y]] representation. | |
| var points = series.map(function(d, i) { | |
| return d.map(function(v, i) { | |
| return [x.call(stack, v, i), y.call(stack, v, i)]; | |
| }); | |
| }); | |
| // Compute the order of series, and permute them. | |
| var orders = order.call(stack, points, index); | |
| series = d3.permute(series, orders); | |
| points = d3.permute(points, orders); | |
| // Compute the baseline… | |
| var offsets = offset.call(stack, points, index); | |
| // And propagate it to other series. | |
| var n = series.length, | |
| m = series[0].length, | |
| i, | |
| j, | |
| o; | |
| for (j = 0; j < m; ++j) { | |
| out.call(stack, series[0][j], o = offsets[j], points[0][j][1]); | |
| for (i = 1; i < n; ++i) { | |
| out.call(stack, series[i][j], o += points[i - 1][j][1], points[i][j][1]); | |
| } | |
| } | |
| return data; | |
| } | |
| stack.values = function(x) { | |
| if (!arguments.length) return values; | |
| values = x; | |
| return stack; | |
| }; | |
| stack.order = function(x) { | |
| if (!arguments.length) return order; | |
| order = typeof x === "function" ? x : d3_layout_stackOrders.get(x) || d3_layout_stackOrderDefault; | |
| return stack; | |
| }; | |
| stack.offset = function(x) { | |
| if (!arguments.length) return offset; | |
| offset = typeof x === "function" ? x : d3_layout_stackOffsets.get(x) || d3_layout_stackOffsetZero; | |
| return stack; | |
| }; | |
| stack.x = function(z) { | |
| if (!arguments.length) return x; | |
| x = z; | |
| return stack; | |
| }; | |
| stack.y = function(z) { | |
| if (!arguments.length) return y; | |
| y = z; | |
| return stack; | |
| }; | |
| stack.out = function(z) { | |
| if (!arguments.length) return out; | |
| out = z; | |
| return stack; | |
| }; | |
| return stack; | |
| } | |
| function d3_layout_stackX(d) { | |
| return d.x; | |
| } | |
| function d3_layout_stackY(d) { | |
| return d.y; | |
| } | |
| function d3_layout_stackOut(d, y0, y) { | |
| d.y0 = y0; | |
| d.y1 = y0+y; | |
| d.y = y; | |
| } | |
| var d3_layout_stackOrders = d3.map({ | |
| "inside-out": function(data) { | |
| var n = data.length, | |
| i, | |
| j, | |
| max = data.map(d3_layout_stackMaxIndex), | |
| sums = data.map(d3_layout_stackReduceSum), | |
| index = d3.range(n).sort(function(a, b) { return max[a] - max[b]; }), | |
| top = 0, | |
| bottom = 0, | |
| tops = [], | |
| bottoms = []; | |
| for (i = 0; i < n; ++i) { | |
| j = index[i]; | |
| if (top < bottom) { | |
| top += sums[j]; | |
| tops.push(j); | |
| } else { | |
| bottom += sums[j]; | |
| bottoms.push(j); | |
| } | |
| } | |
| return bottoms.reverse().concat(tops); | |
| }, | |
| "reverse": function(data) { | |
| return d3.range(data.length).reverse(); | |
| }, | |
| "default": d3_layout_stackOrderDefault | |
| }); | |
| var d3_layout_stackOffsets = d3.map({ | |
| "silhouette": function(data) { | |
| var n = data.length, | |
| m = data[0].length, | |
| sums = [], | |
| max = 0, | |
| i, | |
| j, | |
| o, | |
| y0 = []; | |
| for (j = 0; j < m; ++j) { | |
| for (i = 0, o = 0; i < n; i++) o += data[i][j][1]; | |
| if (o > max) max = o; | |
| sums.push(o); | |
| } | |
| for (j = 0; j < m; ++j) { | |
| y0[j] = (max - sums[j]) / 2; | |
| } | |
| return y0; | |
| }, | |
| "wiggle": function(data) { | |
| var n = data.length, | |
| x = data[0], | |
| m = x.length, | |
| max = 0, | |
| i, | |
| j, | |
| k, | |
| s1, | |
| s2, | |
| s3, | |
| dx, | |
| o, | |
| o0, | |
| y0 = []; | |
| y0[0] = o = o0 = 0; | |
| for (j = 1; j < m; ++j) { | |
| for (i = 0, s1 = 0; i < n; ++i) s1 += data[i][j][1]; | |
| for (i = 0, s2 = 0, dx = x[j][0] - x[j - 1][0]; i < n; ++i) { | |
| for (k = 0, s3 = (data[i][j][1] - data[i][j - 1][1]) / (2 * dx); k < i; ++k) { | |
| s3 += (data[k][j][1] - data[k][j - 1][1]) / dx; | |
| } | |
| s2 += s3 * data[i][j][1]; | |
| } | |
| y0[j] = o -= s1 ? s2 / s1 * dx : 0; | |
| if (o < o0) o0 = o; | |
| } | |
| for (j = 0; j < m; ++j) y0[j] -= o0; | |
| return y0; | |
| }, | |
| "expand": function(data) { | |
| var n = data.length, | |
| m = data[0].length, | |
| k = 1 / n, | |
| i, | |
| j, | |
| o, | |
| y0 = []; | |
| for (j = 0; j < m; ++j) { | |
| for (i = 0, o = 0; i < n; i++) o += data[i][j][1]; | |
| if (o) for (i = 0; i < n; i++) data[i][j][1] /= o; | |
| else for (i = 0; i < n; i++) data[i][j][1] = k; | |
| } | |
| for (j = 0; j < m; ++j) y0[j] = 0; | |
| return y0; | |
| }, | |
| "zero": d3_layout_stackOffsetZero | |
| }); | |
| function d3_layout_stackOrderDefault(data) { | |
| return d3.range(data.length); | |
| } | |
| function d3_layout_stackOffsetZero(data) { | |
| var j = -1, | |
| m = data[0].length, | |
| y0 = []; | |
| while (++j < m) y0[j] = 0; | |
| return y0; | |
| } | |
| function d3_layout_stackMaxIndex(array) { | |
| var i = 1, | |
| j = 0, | |
| v = array[0][1], | |
| k, | |
| n = array.length; | |
| for (; i < n; ++i) { | |
| if ((k = array[i][1]) > v) { | |
| j = i; | |
| v = k; | |
| } | |
| } | |
| return j; | |
| } | |
| function d3_layout_stackReduceSum(d) { | |
| return d.reduce(d3_layout_stackSum, 0); | |
| } | |
| function d3_layout_stackSum(p, d) { | |
| return p + d[1]; | |
| } | |
| d3.layout.histogram = function() { | |
| var frequency = true, | |
| valuer = Number, | |
| ranger = d3_layout_histogramRange, | |
| binner = d3_layout_histogramBinSturges; | |
| function histogram(data, i) { | |
| var bins = [], | |
| values = data.map(valuer, this), | |
| range = ranger.call(this, values, i), | |
| thresholds = binner.call(this, range, values, i), | |
| bin, | |
| i = -1, | |
| n = values.length, | |
| m = thresholds.length - 1, | |
| k = frequency ? 1 : 1 / n, | |
| x; | |
| // Initialize the bins. | |
| while (++i < m) { | |
| bin = bins[i] = []; | |
| bin.dx = thresholds[i + 1] - (bin.x = thresholds[i]); | |
| bin.y = 0; | |
| } | |
| // Fill the bins, ignoring values outside the range. | |
| if (m > 0) { | |
| i = -1; while(++i < n) { | |
| x = values[i]; | |
| if (x >= range[0] && x <= range[1]) { | |
| bin = bins[d3.bisect(thresholds, x, 1, m) - 1]; | |
| bin.y += k; | |
| bin.push(data[i]); | |
| } | |
| } | |
| } | |
| return bins; | |
| } | |
| // Specifies how to extract a value from the associated data. The default | |
| // value function is `Number`, which is equivalent to the identity function. | |
| histogram.value = function(x) { | |
| if (!arguments.length) return valuer; | |
| valuer = x; | |
| return histogram; | |
| }; | |
| // Specifies the range of the histogram. Values outside the specified range | |
| // will be ignored. The argument `x` may be specified either as a two-element | |
| // array representing the minimum and maximum value of the range, or as a | |
| // function that returns the range given the array of values and the current | |
| // index `i`. The default range is the extent (minimum and maximum) of the | |
| // values. | |
| histogram.range = function(x) { | |
| if (!arguments.length) return ranger; | |
| ranger = d3_functor(x); | |
| return histogram; | |
| }; | |
| // Specifies how to bin values in the histogram. The argument `x` may be | |
| // specified as a number, in which case the range of values will be split | |
| // uniformly into the given number of bins. Or, `x` may be an array of | |
| // threshold values, defining the bins; the specified array must contain the | |
| // rightmost (upper) value, thus specifying n + 1 values for n bins. Or, `x` | |
| // may be a function which is evaluated, being passed the range, the array of | |
| // values, and the current index `i`, returning an array of thresholds. The | |
| // default bin function will divide the values into uniform bins using | |
| // Sturges' formula. | |
| histogram.bins = function(x) { | |
| if (!arguments.length) return binner; | |
| binner = typeof x === "number" | |
| ? function(range) { return d3_layout_histogramBinFixed(range, x); } | |
| : d3_functor(x); | |
| return histogram; | |
| }; | |
| // Specifies whether the histogram's `y` value is a count (frequency) or a | |
| // probability (density). The default value is true. | |
| histogram.frequency = function(x) { | |
| if (!arguments.length) return frequency; | |
| frequency = !!x; | |
| return histogram; | |
| }; | |
| return histogram; | |
| }; | |
| function d3_layout_histogramBinSturges(range, values) { | |
| return d3_layout_histogramBinFixed(range, Math.ceil(Math.log(values.length) / Math.LN2 + 1)); | |
| } | |
| function d3_layout_histogramBinFixed(range, n) { | |
| var x = -1, | |
| b = +range[0], | |
| m = (range[1] - b) / n, | |
| f = []; | |
| while (++x <= n) f[x] = m * x + b; | |
| return f; | |
| } | |
| function d3_layout_histogramRange(values) { | |
| return [d3.min(values), d3.max(values)]; | |
| } | |
| d3.layout.hierarchy = function() { | |
| var sort = d3_layout_hierarchySort, | |
| children = d3_layout_hierarchyChildren, | |
| value = d3_layout_hierarchyValue; | |
| // Recursively compute the node depth and value. | |
| // Also converts the data representation into a standard hierarchy structure. | |
| function recurse(data, depth, nodes) { | |
| var childs = children.call(hierarchy, data, depth), | |
| node = d3_layout_hierarchyInline ? data : {data: data}; | |
| node.depth = depth; | |
| nodes.push(node); | |
| if (childs && (n = childs.length)) { | |
| var i = -1, | |
| n, | |
| c = node.children = [], | |
| v = 0, | |
| j = depth + 1, | |
| d; | |
| while (++i < n) { | |
| d = recurse(childs[i], j, nodes); | |
| d.parent = node; | |
| c.push(d); | |
| v += d.value; | |
| } | |
| if (sort) c.sort(sort); | |
| if (value) node.value = v; | |
| } else if (value) { | |
| node.value = +value.call(hierarchy, data, depth) || 0; | |
| } | |
| return node; | |
| } | |
| // Recursively re-evaluates the node value. | |
| function revalue(node, depth) { | |
| var children = node.children, | |
| v = 0; | |
| if (children && (n = children.length)) { | |
| var i = -1, | |
| n, | |
| j = depth + 1; | |
| while (++i < n) v += revalue(children[i], j); | |
| } else if (value) { | |
| v = +value.call(hierarchy, d3_layout_hierarchyInline ? node : node.data, depth) || 0; | |
| } | |
| if (value) node.value = v; | |
| return v; | |
| } | |
| function hierarchy(d) { | |
| var nodes = []; | |
| recurse(d, 0, nodes); | |
| return nodes; | |
| } | |
| hierarchy.sort = function(x) { | |
| if (!arguments.length) return sort; | |
| sort = x; | |
| return hierarchy; | |
| }; | |
| hierarchy.children = function(x) { | |
| if (!arguments.length) return children; | |
| children = x; | |
| return hierarchy; | |
| }; | |
| // Returns an array source+target objects for the specified nodes. | |
| hierarchy.links = function(nodes) { | |
| return d3.merge(nodes.map(function(parent) { | |
| return (parent.children || []).map(function(child) { | |
| return {source: parent, target: child}; | |
| }); | |
| })); | |
| }; | |
| // If the new API is used, enabling inlining. | |
| hierarchy.nodes = function(d) { | |
| d3_layout_hierarchyInline = true; | |
| return (hierarchy.nodes = hierarchy)(d); | |
| }; | |
| hierarchy.value = function(x) { | |
| if (!arguments.length) return value; | |
| value = x; | |
| return hierarchy; | |
| }; | |
| // Re-evaluates the `value` property for the specified hierarchy. | |
| hierarchy.revalue = function(root) { | |
| revalue(root, 0); | |
| return root; | |
| }; | |
| return hierarchy; | |
| }; | |
| // A method assignment helper for hierarchy subclasses. | |
| function d3_layout_hierarchyRebind(object, hierarchy) { | |
| d3.rebind(object, hierarchy, "sort", "children", "value"); | |
| object.links = hierarchy.links; | |
| // If the new API is used, enabling inlining. | |
| object.nodes = function(d) { | |
| d3_layout_hierarchyInline = true; | |
| return (object.nodes = object)(d); | |
| }; | |
| return object; | |
| } | |
| function d3_layout_hierarchyChildren(d) { | |
| return d.children; | |
| } | |
| function d3_layout_hierarchyValue(d) { | |
| return d.value; | |
| } | |
| function d3_layout_hierarchySort(a, b) { | |
| return b.value - a.value; | |
| } | |
| // For backwards-compatibility, don't enable inlining by default. | |
| var d3_layout_hierarchyInline = false; | |
| d3.layout.pack = function() { | |
| var hierarchy = d3.layout.hierarchy().sort(d3_layout_packSort), | |
| padding = 0, | |
| size = [1, 1]; | |
| function pack(d, i) { | |
| var nodes = hierarchy.call(this, d, i), | |
| root = nodes[0]; | |
| // Recursively compute the layout. | |
| root.x = 0; | |
| root.y = 0; | |
| d3_layout_treeVisitAfter(root, function(d) { d.r = Math.sqrt(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; | |
| 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); | |
| return nodes; | |
| } | |
| pack.size = function(x) { | |
| if (!arguments.length) return size; | |
| size = x; | |
| return pack; | |
| }; | |
| pack.padding = function(_) { | |
| if (!arguments.length) return padding; | |
| padding = +_; | |
| return pack; | |
| }; | |
| return d3_layout_hierarchyRebind(pack, hierarchy); | |
| }; | |
| function d3_layout_packSort(a, b) { | |
| return a.value - b.value; | |
| } | |
| function d3_layout_packInsert(a, b) { | |
| var c = a._pack_next; | |
| a._pack_next = b; | |
| b._pack_prev = a; | |
| b._pack_next = c; | |
| c._pack_prev = b; | |
| } | |
| function d3_layout_packSplice(a, b) { | |
| a._pack_next = b; | |
| b._pack_prev = a; | |
| } | |
| 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 | |
| } | |
| function d3_layout_packSiblings(node) { | |
| if (!(nodes = node.children) || !(n = nodes.length)) return; | |
| var nodes, | |
| xMin = Infinity, | |
| xMax = -Infinity, | |
| yMin = Infinity, | |
| yMax = -Infinity, | |
| a, b, c, i, j, k, n; | |
| function bound(node) { | |
| xMin = Math.min(node.x - node.r, xMin); | |
| xMax = Math.max(node.x + node.r, xMax); | |
| yMin = Math.min(node.y - node.r, yMin); | |
| yMax = Math.max(node.y + node.r, yMax); | |
| } | |
| // Create node links. | |
| nodes.forEach(d3_layout_packLink); | |
| // Create first node. | |
| a = nodes[0]; | |
| a.x = -a.r; | |
| a.y = 0; | |
| bound(a); | |
| // Create second node. | |
| if (n > 1) { | |
| b = nodes[1]; | |
| b.x = b.r; | |
| b.y = 0; | |
| bound(b); | |
| // Create third node and build chain. | |
| if (n > 2) { | |
| c = nodes[2]; | |
| d3_layout_packPlace(a, b, c); | |
| bound(c); | |
| d3_layout_packInsert(a, c); | |
| a._pack_prev = c; | |
| d3_layout_packInsert(c, b); | |
| b = a._pack_next; | |
| // Now iterate through the rest. | |
| for (i = 3; i < n; i++) { | |
| d3_layout_packPlace(a, b, c = nodes[i]); | |
| // Search for the closest intersection. | |
| var isect = 0, s1 = 1, s2 = 1; | |
| for (j = b._pack_next; j !== b; j = j._pack_next, s1++) { | |
| if (d3_layout_packIntersects(j, c)) { | |
| isect = 1; | |
| break; | |
| } | |
| } | |
| if (isect == 1) { | |
| for (k = a._pack_prev; k !== j._pack_prev; k = k._pack_prev, s2++) { | |
| if (d3_layout_packIntersects(k, c)) { | |
| break; | |
| } | |
| } | |
| } | |
| // Update node chain. | |
| if (isect) { | |
| if (s1 < s2 || (s1 == s2 && b.r < a.r)) d3_layout_packSplice(a, b = j); | |
| else d3_layout_packSplice(a = k, b); | |
| i--; | |
| } else { | |
| d3_layout_packInsert(a, c); | |
| b = c; | |
| bound(c); | |
| } | |
| } | |
| } | |
| } | |
| // Re-center the circles and compute the encompassing radius. | |
| var cx = (xMin + xMax) / 2, | |
| cy = (yMin + yMax) / 2, | |
| cr = 0; | |
| for (i = 0; i < n; i++) { | |
| c = nodes[i]; | |
| c.x -= cx; | |
| c.y -= cy; | |
| cr = Math.max(cr, c.r + Math.sqrt(c.x * c.x + c.y * c.y)); | |
| } | |
| node.r = cr; | |
| // Remove node links. | |
| nodes.forEach(d3_layout_packUnlink); | |
| } | |
| function d3_layout_packLink(node) { | |
| node._pack_next = node._pack_prev = node; | |
| } | |
| function d3_layout_packUnlink(node) { | |
| delete node._pack_next; | |
| delete node._pack_prev; | |
| } | |
| function d3_layout_packTransform(node, x, y, k) { | |
| var children = node.children; | |
| node.x = (x += k * node.x); | |
| node.y = (y += k * node.y); | |
| node.r *= k; | |
| if (children) { | |
| var i = -1, n = children.length; | |
| while (++i < n) d3_layout_packTransform(children[i], x, y, k); | |
| } | |
| } | |
| function d3_layout_packPlace(a, b, c) { | |
| var db = a.r + c.r, | |
| dx = b.x - a.x, | |
| dy = b.y - a.y; | |
| if (db && (dx || dy)) { | |
| var da = b.r + c.r, | |
| dc = dx * dx + dy * dy; | |
| da *= da; | |
| db *= db; | |
| var x = .5 + (db - da) / (2 * dc), | |
| y = Math.sqrt(Math.max(0, 2 * da * (db + dc) - (db -= dc) * db - da * da)) / (2 * dc); | |
| c.x = a.x + x * dx + y * dy; | |
| c.y = a.y + x * dy - y * dx; | |
| } else { | |
| c.x = a.x + db; | |
| c.y = a.y; | |
| } | |
| } | |
| // Implements a hierarchical layout using the cluster (or dendrogram) | |
| // algorithm. | |
| d3.layout.cluster = function() { | |
| var hierarchy = d3.layout.hierarchy().sort(null).value(null), | |
| separation = d3_layout_treeSeparation, | |
| size = [1, 1]; // width, height | |
| function cluster(d, i) { | |
| var nodes = hierarchy.call(this, d, i), | |
| root = nodes[0], | |
| previousNode, | |
| x = 0, | |
| kx, | |
| ky; | |
| // First walk, computing the initial x & y values. | |
| d3_layout_treeVisitAfter(root, function(node) { | |
| var children = node.children; | |
| if (children && children.length) { | |
| node.x = d3_layout_clusterX(children); | |
| node.y = d3_layout_clusterY(children); | |
| } else { | |
| node.x = previousNode ? x += separation(node, previousNode) : 0; | |
| node.y = 0; | |
| previousNode = node; | |
| } | |
| }); | |
| // Compute the left-most, right-most, and depth-most nodes for extents. | |
| var left = d3_layout_clusterLeft(root), | |
| right = d3_layout_clusterRight(root), | |
| x0 = left.x - separation(left, right) / 2, | |
| x1 = right.x + separation(right, left) / 2; | |
| // Second walk, normalizing x & y to the desired size. | |
| d3_layout_treeVisitAfter(root, function(node) { | |
| node.x = (node.x - x0) / (x1 - x0) * size[0]; | |
| node.y = (1 - (root.y ? node.y / root.y : 1)) * size[1]; | |
| }); | |
| return nodes; | |
| } | |
| cluster.separation = function(x) { | |
| if (!arguments.length) return separation; | |
| separation = x; | |
| return cluster; | |
| }; | |
| cluster.size = function(x) { | |
| if (!arguments.length) return size; | |
| size = x; | |
| return cluster; | |
| }; | |
| return d3_layout_hierarchyRebind(cluster, hierarchy); | |
| }; | |
| function d3_layout_clusterY(children) { | |
| return 1 + d3.max(children, function(child) { | |
| return child.y; | |
| }); | |
| } | |
| function d3_layout_clusterX(children) { | |
| return children.reduce(function(x, child) { | |
| return x + child.x; | |
| }, 0) / children.length; | |
| } | |
| function d3_layout_clusterLeft(node) { | |
| var children = node.children; | |
| return children && children.length ? d3_layout_clusterLeft(children[0]) : node; | |
| } | |
| function d3_layout_clusterRight(node) { | |
| var children = node.children, n; | |
| return children && (n = children.length) ? d3_layout_clusterRight(children[n - 1]) : node; | |
| } | |
| // Node-link tree diagram using the Reingold-Tilford "tidy" algorithm | |
| d3.layout.tree = function() { | |
| var hierarchy = d3.layout.hierarchy().sort(null).value(null), | |
| separation = d3_layout_treeSeparation, | |
| size = [1, 1]; // width, height | |
| function tree(d, i) { | |
| var nodes = hierarchy.call(this, d, i), | |
| root = nodes[0]; | |
| function firstWalk(node, previousSibling) { | |
| var children = node.children, | |
| layout = node._tree; | |
| if (children && (n = children.length)) { | |
| var n, | |
| firstChild = children[0], | |
| previousChild, | |
| ancestor = firstChild, | |
| child, | |
| i = -1; | |
| while (++i < n) { | |
| child = children[i]; | |
| firstWalk(child, previousChild); | |
| ancestor = apportion(child, previousChild, ancestor); | |
| previousChild = child; | |
| } | |
| d3_layout_treeShift(node); | |
| var midpoint = .5 * (firstChild._tree.prelim + child._tree.prelim); | |
| if (previousSibling) { | |
| layout.prelim = previousSibling._tree.prelim + separation(node, previousSibling); | |
| layout.mod = layout.prelim - midpoint; | |
| } else { | |
| layout.prelim = midpoint; | |
| } | |
| } else { | |
| if (previousSibling) { | |
| layout.prelim = previousSibling._tree.prelim + separation(node, previousSibling); | |
| } | |
| } | |
| } | |
| function secondWalk(node, x) { | |
| node.x = node._tree.prelim + x; | |
| var children = node.children; | |
| if (children && (n = children.length)) { | |
| var i = -1, | |
| n; | |
| x += node._tree.mod; | |
| while (++i < n) { | |
| secondWalk(children[i], x); | |
| } | |
| } | |
| } | |
| function apportion(node, previousSibling, ancestor) { | |
| if (previousSibling) { | |
| var vip = node, | |
| vop = node, | |
| vim = previousSibling, | |
| vom = node.parent.children[0], | |
| sip = vip._tree.mod, | |
| sop = vop._tree.mod, | |
| sim = vim._tree.mod, | |
| som = vom._tree.mod, | |
| shift; | |
| while (vim = d3_layout_treeRight(vim), vip = d3_layout_treeLeft(vip), vim && vip) { | |
| vom = d3_layout_treeLeft(vom); | |
| vop = d3_layout_treeRight(vop); | |
| vop._tree.ancestor = node; | |
| shift = vim._tree.prelim + sim - vip._tree.prelim - sip + separation(vim, vip); | |
| if (shift > 0) { | |
| d3_layout_treeMove(d3_layout_treeAncestor(vim, node, ancestor), node, shift); | |
| sip += shift; | |
| sop += shift; | |
| } | |
| sim += vim._tree.mod; | |
| sip += vip._tree.mod; | |
| som += vom._tree.mod; | |
| sop += vop._tree.mod; | |
| } | |
| if (vim && !d3_layout_treeRight(vop)) { | |
| vop._tree.thread = vim; | |
| vop._tree.mod += sim - sop; | |
| } | |
| if (vip && !d3_layout_treeLeft(vom)) { | |
| vom._tree.thread = vip; | |
| vom._tree.mod += sip - som; | |
| ancestor = node; | |
| } | |
| } | |
| return ancestor; | |
| } | |
| // Initialize temporary layout variables. | |
| d3_layout_treeVisitAfter(root, function(node, previousSibling) { | |
| node._tree = { | |
| ancestor: node, | |
| prelim: 0, | |
| mod: 0, | |
| change: 0, | |
| shift: 0, | |
| number: previousSibling ? previousSibling._tree.number + 1 : 0 | |
| }; | |
| }); | |
| // Compute the layout using Buchheim et al.'s algorithm. | |
| firstWalk(root); | |
| secondWalk(root, -root._tree.prelim); | |
| // Compute the left-most, right-most, and depth-most nodes for extents. | |
| var left = d3_layout_treeSearch(root, d3_layout_treeLeftmost), | |
| right = d3_layout_treeSearch(root, d3_layout_treeRightmost), | |
| deep = d3_layout_treeSearch(root, d3_layout_treeDeepest), | |
| x0 = left.x - separation(left, right) / 2, | |
| x1 = right.x + separation(right, left) / 2, | |
| y1 = deep.depth || 1; | |
| // Clear temporary layout variables; transform x and y. | |
| d3_layout_treeVisitAfter(root, function(node) { | |
| node.x = (node.x - x0) / (x1 - x0) * size[0]; | |
| node.y = node.depth / y1 * size[1]; | |
| delete node._tree; | |
| }); | |
| return nodes; | |
| } | |
| tree.separation = function(x) { | |
| if (!arguments.length) return separation; | |
| separation = x; | |
| return tree; | |
| }; | |
| tree.size = function(x) { | |
| if (!arguments.length) return size; | |
| size = x; | |
| return tree; | |
| }; | |
| return d3_layout_hierarchyRebind(tree, hierarchy); | |
| }; | |
| function d3_layout_treeSeparation(a, b) { | |
| return a.parent == b.parent ? 1 : 2; | |
| } | |
| // function d3_layout_treeSeparationRadial(a, b) { | |
| // return (a.parent == b.parent ? 1 : 2) / a.depth; | |
| // } | |
| function d3_layout_treeLeft(node) { | |
| var children = node.children; | |
| return children && children.length ? children[0] : node._tree.thread; | |
| } | |
| function d3_layout_treeRight(node) { | |
| var children = node.children, | |
| n; | |
| return children && (n = children.length) ? children[n - 1] : node._tree.thread; | |
| } | |
| function d3_layout_treeSearch(node, compare) { | |
| var children = node.children; | |
| if (children && (n = children.length)) { | |
| var child, | |
| n, | |
| i = -1; | |
| while (++i < n) { | |
| if (compare(child = d3_layout_treeSearch(children[i], compare), node) > 0) { | |
| node = child; | |
| } | |
| } | |
| } | |
| return node; | |
| } | |
| function d3_layout_treeRightmost(a, b) { | |
| return a.x - b.x; | |
| } | |
| function d3_layout_treeLeftmost(a, b) { | |
| return b.x - a.x; | |
| } | |
| function d3_layout_treeDeepest(a, b) { | |
| return a.depth - b.depth; | |
| } | |
| function d3_layout_treeVisitAfter(node, callback) { | |
| function visit(node, previousSibling) { | |
| var children = node.children; | |
| if (children && (n = children.length)) { | |
| var child, | |
| previousChild = null, | |
| i = -1, | |
| n; | |
| while (++i < n) { | |
| child = children[i]; | |
| visit(child, previousChild); | |
| previousChild = child; | |
| } | |
| } | |
| callback(node, previousSibling); | |
| } | |
| visit(node, null); | |
| } | |
| function d3_layout_treeShift(node) { | |
| var shift = 0, | |
| change = 0, | |
| children = node.children, | |
| i = children.length, | |
| child; | |
| while (--i >= 0) { | |
| child = children[i]._tree; | |
| child.prelim += shift; | |
| child.mod += shift; | |
| shift += child.shift + (change += child.change); | |
| } | |
| } | |
| function d3_layout_treeMove(ancestor, node, shift) { | |
| ancestor = ancestor._tree; | |
| node = node._tree; | |
| var change = shift / (node.number - ancestor.number); | |
| ancestor.change += change; | |
| node.change -= change; | |
| node.shift += shift; | |
| node.prelim += shift; | |
| node.mod += shift; | |
| } | |
| function d3_layout_treeAncestor(vim, node, ancestor) { | |
| return vim._tree.ancestor.parent == node.parent | |
| ? vim._tree.ancestor | |
| : ancestor; | |
| } | |
| // Squarified Treemaps by Mark Bruls, Kees Huizing, and Jarke J. van Wijk | |
| // Modified to support a target aspect ratio by Jeff Heer | |
| d3.layout.treemap = function() { | |
| var hierarchy = d3.layout.hierarchy(), | |
| round = Math.round, | |
| size = [1, 1], // width, height | |
| padding = null, | |
| pad = d3_layout_treemapPadNull, | |
| sticky = false, | |
| stickies, | |
| ratio = 0.5 * (1 + Math.sqrt(5)); // golden ratio | |
| // Compute the area for each child based on value & scale. | |
| function scale(children, k) { | |
| var i = -1, | |
| n = children.length, | |
| child, | |
| area; | |
| while (++i < n) { | |
| area = (child = children[i]).value * (k < 0 ? 0 : k); | |
| child.area = isNaN(area) || area <= 0 ? 0 : area; | |
| } | |
| } | |
| // Recursively arranges the specified node's children into squarified rows. | |
| function squarify(node) { | |
| var children = node.children; | |
| if (children && children.length) { | |
| var rect = pad(node), | |
| row = [], | |
| remaining = children.slice(), // copy-on-write | |
| child, | |
| best = Infinity, // the best row score so far | |
| score, // the current row score | |
| u = Math.min(rect.dx, rect.dy), // initial orientation | |
| n; | |
| scale(remaining, rect.dx * rect.dy / node.value); | |
| row.area = 0; | |
| while ((n = remaining.length) > 0) { | |
| row.push(child = remaining[n - 1]); | |
| row.area += child.area; | |
| if ((score = worst(row, u)) <= best) { // continue with this orientation | |
| remaining.pop(); | |
| best = score; | |
| } else { // abort, and try a different orientation | |
| row.area -= row.pop().area; | |
| position(row, u, rect, false); | |
| u = Math.min(rect.dx, rect.dy); | |
| row.length = row.area = 0; | |
| best = Infinity; | |
| } | |
| } | |
| if (row.length) { | |
| position(row, u, rect, true); | |
| row.length = row.area = 0; | |
| } | |
| children.forEach(squarify); | |
| } | |
| } | |
| // Recursively resizes the specified node's children into existing rows. | |
| // Preserves the existing layout! | |
| function stickify(node) { | |
| var children = node.children; | |
| if (children && children.length) { | |
| var rect = pad(node), | |
| remaining = children.slice(), // copy-on-write | |
| child, | |
| row = []; | |
| scale(remaining, rect.dx * rect.dy / node.value); | |
| row.area = 0; | |
| while (child = remaining.pop()) { | |
| row.push(child); | |
| row.area += child.area; | |
| if (child.z != null) { | |
| position(row, child.z ? rect.dx : rect.dy, rect, !remaining.length); | |
| row.length = row.area = 0; | |
| } | |
| } | |
| children.forEach(stickify); | |
| } | |
| } | |
| // Computes the score for the specified row, as the worst aspect ratio. | |
| function worst(row, u) { | |
| var s = row.area, | |
| r, | |
| rmax = 0, | |
| rmin = Infinity, | |
| i = -1, | |
| n = row.length; | |
| while (++i < n) { | |
| if (!(r = row[i].area)) continue; | |
| if (r < rmin) rmin = r; | |
| if (r > rmax) rmax = r; | |
| } | |
| s *= s; | |
| u *= u; | |
| return s | |
| ? Math.max((u * rmax * ratio) / s, s / (u * rmin * ratio)) | |
| : Infinity; | |
| } | |
| // Positions the specified row of nodes. Modifies `rect`. | |
| function position(row, u, rect, flush) { | |
| var i = -1, | |
| n = row.length, | |
| x = rect.x, | |
| y = rect.y, | |
| v = u ? round(row.area / u) : 0, | |
| o; | |
| if (u == rect.dx) { // horizontal subdivision | |
| if (flush || v > rect.dy) v = rect.dy; // over+underflow | |
| while (++i < n) { | |
| o = row[i]; | |
| o.x = x; | |
| o.y = y; | |
| o.dy = v; | |
| x += o.dx = Math.min(rect.x + rect.dx - x, v ? round(o.area / v) : 0); | |
| } | |
| o.z = true; | |
| o.dx += rect.x + rect.dx - x; // rounding error | |
| rect.y += v; | |
| rect.dy -= v; | |
| } else { // vertical subdivision | |
| if (flush || v > rect.dx) v = rect.dx; // over+underflow | |
| while (++i < n) { | |
| o = row[i]; | |
| o.x = x; | |
| o.y = y; | |
| o.dx = v; | |
| y += o.dy = Math.min(rect.y + rect.dy - y, v ? round(o.area / v) : 0); | |
| } | |
| o.z = false; | |
| o.dy += rect.y + rect.dy - y; // rounding error | |
| rect.x += v; | |
| rect.dx -= v; | |
| } | |
| } | |
| function treemap(d) { | |
| var nodes = stickies || hierarchy(d), | |
| root = nodes[0]; | |
| root.x = 0; | |
| root.y = 0; | |
| root.dx = size[0]; | |
| root.dy = size[1]; | |
| if (stickies) hierarchy.revalue(root); | |
| scale([root], root.dx * root.dy / root.value); | |
| (stickies ? stickify : squarify)(root); | |
| if (sticky) stickies = nodes; | |
| return nodes; | |
| } | |
| treemap.size = function(x) { | |
| if (!arguments.length) return size; | |
| size = x; | |
| return treemap; | |
| }; | |
| treemap.padding = function(x) { | |
| if (!arguments.length) return padding; | |
| function padFunction(node) { | |
| var p = x.call(treemap, node, node.depth); | |
| return p == null | |
| ? d3_layout_treemapPadNull(node) | |
| : d3_layout_treemapPad(node, typeof p === "number" ? [p, p, p, p] : p); | |
| } | |
| function padConstant(node) { | |
| return d3_layout_treemapPad(node, x); | |
| } | |
| var type; | |
| pad = (padding = x) == null ? d3_layout_treemapPadNull | |
| : (type = typeof x) === "function" ? padFunction | |
| : type === "number" ? (x = [x, x, x, x], padConstant) | |
| : padConstant; | |
| return treemap; | |
| }; | |
| treemap.round = function(x) { | |
| if (!arguments.length) return round != Number; | |
| round = x ? Math.round : Number; | |
| return treemap; | |
| }; | |
| treemap.sticky = function(x) { | |
| if (!arguments.length) return sticky; | |
| sticky = x; | |
| stickies = null; | |
| return treemap; | |
| }; | |
| treemap.ratio = function(x) { | |
| if (!arguments.length) return ratio; | |
| ratio = x; | |
| return treemap; | |
| }; | |
| return d3_layout_hierarchyRebind(treemap, hierarchy); | |
| }; | |
| function d3_layout_treemapPadNull(node) { | |
| return {x: node.x, y: node.y, dx: node.dx, dy: node.dy}; | |
| } | |
| function d3_layout_treemapPad(node, padding) { | |
| var x = node.x + padding[3], | |
| y = node.y + padding[0], | |
| dx = node.dx - padding[1] - padding[3], | |
| dy = node.dy - padding[0] - padding[2]; | |
| if (dx < 0) { x += dx / 2; dx = 0; } | |
| if (dy < 0) { y += dy / 2; dy = 0; } | |
| return {x: x, y: y, dx: dx, dy: dy}; | |
| } | |
| function d3_dsv(delimiter, mimeType) { | |
| var reParse = new RegExp("\r\n|[" + delimiter + "\r\n]", "g"), // field separator regex | |
| reFormat = new RegExp("[\"" + delimiter + "\n]"), | |
| delimiterCode = delimiter.charCodeAt(0); | |
| function dsv(url, callback) { | |
| d3.text(url, mimeType, function(text) { | |
| callback(text && dsv.parse(text)); | |
| }); | |
| } | |
| dsv.parse = function(text) { | |
| var header; | |
| return dsv.parseRows(text, function(row, i) { | |
| if (i) { | |
| var o = {}, j = -1, m = header.length; | |
| while (++j < m) o[header[j]] = j < row.length ? row[j] : null; | |
| return o; | |
| } else { | |
| header = row; | |
| return null; | |
| } | |
| }); | |
| }; | |
| dsv.parseRows = function(text, f) { | |
| var EOL = {}, // sentinel value for end-of-line | |
| EOF = {}, // sentinel value for end-of-file | |
| rows = [], // output rows | |
| n = 0, // the current line number | |
| t, // the current token | |
| eol; // is the current token followed by EOL? | |
| reParse.lastIndex = 0; // work-around bug in FF 3.6 | |
| function token() { | |
| if (reParse.lastIndex >= text.length) return EOF; // special case: end of file | |
| if (eol) { eol = false; return EOL; } // special case: end of line | |
| // special case: quotes | |
| var j = reParse.lastIndex; | |
| if (text.charCodeAt(j) === 34) { | |
| var i = j; | |
| while (i++ < text.length) { | |
| if (text.charCodeAt(i) === 34) { | |
| if (text.charCodeAt(i + 1) !== 34) break; | |
| i++; | |
| } | |
| } | |
| reParse.lastIndex = i + 2; | |
| var c = text.charCodeAt(i + 1); | |
| if (c === 13) { | |
| eol = true; | |
| if (text.charCodeAt(i + 2) === 10) reParse.lastIndex++; | |
| } else if (c === 10) { | |
| eol = true; | |
| } | |
| return text.substring(j + 1, i).replace(/""/g, "\""); | |
| } | |
| // common case | |
| var m = reParse.exec(text); | |
| if (m) { | |
| eol = m[0].charCodeAt(0) !== delimiterCode; | |
| return text.substring(j, m.index); | |
| } | |
| reParse.lastIndex = text.length; | |
| return text.substring(j); | |
| } | |
| while ((t = token()) !== EOF) { | |
| var a = []; | |
| while (t !== EOL && t !== EOF) { | |
| a.push(t); | |
| t = token(); | |
| } | |
| if (f && !(a = f(a, n++))) continue; | |
| rows.push(a); | |
| } | |
| return rows; | |
| }; | |
| dsv.format = function(rows) { | |
| return rows.map(formatRow).join("\n"); | |
| }; | |
| function formatRow(row) { | |
| return row.map(formatValue).join(delimiter); | |
| } | |
| function formatValue(text) { | |
| return reFormat.test(text) ? "\"" + text.replace(/\"/g, "\"\"") + "\"" : text; | |
| } | |
| return dsv; | |
| } | |
| d3.csv = d3_dsv(",", "text/csv"); | |
| d3.tsv = d3_dsv("\t", "text/tab-separated-values"); | |
| d3.geo = {}; | |
| var d3_geo_radians = Math.PI / 180; | |
| // TODO clip input coordinates on opposite hemisphere | |
| d3.geo.azimuthal = function() { | |
| var mode = "orthographic", // or stereographic, gnomonic, equidistant or equalarea | |
| origin, | |
| scale = 200, | |
| translate = [480, 250], | |
| x0, | |
| y0, | |
| cy0, | |
| sy0; | |
| function azimuthal(coordinates) { | |
| var x1 = coordinates[0] * d3_geo_radians - x0, | |
| y1 = coordinates[1] * d3_geo_radians, | |
| cx1 = Math.cos(x1), | |
| sx1 = Math.sin(x1), | |
| cy1 = Math.cos(y1), | |
| sy1 = Math.sin(y1), | |
| cc = mode !== "orthographic" ? sy0 * sy1 + cy0 * cy1 * cx1 : null, | |
| c, | |
| k = mode === "stereographic" ? 1 / (1 + cc) | |
| : mode === "gnomonic" ? 1 / cc | |
| : mode === "equidistant" ? (c = Math.acos(cc), c ? c / Math.sin(c) : 0) | |
| : mode === "equalarea" ? Math.sqrt(2 / (1 + cc)) | |
| : 1, | |
| x = k * cy1 * sx1, | |
| y = k * (sy0 * cy1 * cx1 - cy0 * sy1); | |
| return [ | |
| scale * x + translate[0], | |
| scale * y + translate[1] | |
| ]; | |
| } | |
| azimuthal.invert = function(coordinates) { | |
| var x = (coordinates[0] - translate[0]) / scale, | |
| y = (coordinates[1] - translate[1]) / scale, | |
| p = Math.sqrt(x * x + y * y), | |
| c = mode === "stereographic" ? 2 * Math.atan(p) | |
| : mode === "gnomonic" ? Math.atan(p) | |
| : mode === "equidistant" ? p | |
| : mode === "equalarea" ? 2 * Math.asin(.5 * p) | |
| : Math.asin(p), | |
| sc = Math.sin(c), | |
| cc = Math.cos(c); | |
| return [ | |
| (x0 + Math.atan2(x * sc, p * cy0 * cc + y * sy0 * sc)) / d3_geo_radians, | |
| Math.asin(cc * sy0 - (p ? (y * sc * cy0) / p : 0)) / d3_geo_radians | |
| ]; | |
| }; | |
| azimuthal.mode = function(x) { | |
| if (!arguments.length) return mode; | |
| mode = x + ""; | |
| return azimuthal; | |
| }; | |
| azimuthal.origin = function(x) { | |
| if (!arguments.length) return origin; | |
| origin = x; | |
| x0 = origin[0] * d3_geo_radians; | |
| y0 = origin[1] * d3_geo_radians; | |
| cy0 = Math.cos(y0); | |
| sy0 = Math.sin(y0); | |
| return azimuthal; | |
| }; | |
| azimuthal.scale = function(x) { | |
| if (!arguments.length) return scale; | |
| scale = +x; | |
| return azimuthal; | |
| }; | |
| azimuthal.translate = function(x) { | |
| if (!arguments.length) return translate; | |
| translate = [+x[0], +x[1]]; | |
| return azimuthal; | |
| }; | |
| return azimuthal.origin([0, 0]); | |
| }; | |
| // Derived from Tom Carden's Albers implementation for Protovis. | |
| // http://gist.github.com/476238 | |
| // http://mathworld.wolfram.com/AlbersEqual-AreaConicProjection.html | |
| d3.geo.albers = function() { | |
| var origin = [-98, 38], | |
| parallels = [29.5, 45.5], | |
| scale = 1000, | |
| translate = [480, 250], | |
| lng0, // d3_geo_radians * origin[0] | |
| n, | |
| C, | |
| p0; | |
| function albers(coordinates) { | |
| var t = n * (d3_geo_radians * coordinates[0] - lng0), | |
| p = Math.sqrt(C - 2 * n * Math.sin(d3_geo_radians * coordinates[1])) / n; | |
| return [ | |
| scale * p * Math.sin(t) + translate[0], | |
| scale * (p * Math.cos(t) - p0) + translate[1] | |
| ]; | |
| } | |
| albers.invert = function(coordinates) { | |
| var x = (coordinates[0] - translate[0]) / scale, | |
| y = (coordinates[1] - translate[1]) / scale, | |
| p0y = p0 + y, | |
| t = Math.atan2(x, p0y), | |
| p = Math.sqrt(x * x + p0y * p0y); | |
| return [ | |
| (lng0 + t / n) / d3_geo_radians, | |
| Math.asin((C - p * p * n * n) / (2 * n)) / d3_geo_radians | |
| ]; | |
| }; | |
| function reload() { | |
| var phi1 = d3_geo_radians * parallels[0], | |
| phi2 = d3_geo_radians * parallels[1], | |
| lat0 = d3_geo_radians * origin[1], | |
| s = Math.sin(phi1), | |
| c = Math.cos(phi1); | |
| lng0 = d3_geo_radians * origin[0]; | |
| n = .5 * (s + Math.sin(phi2)); | |
| C = c * c + 2 * n * s; | |
| p0 = Math.sqrt(C - 2 * n * Math.sin(lat0)) / n; | |
| return albers; | |
| } | |
| albers.origin = function(x) { | |
| if (!arguments.length) return origin; | |
| origin = [+x[0], +x[1]]; | |
| return reload(); | |
| }; | |
| albers.parallels = function(x) { | |
| if (!arguments.length) return parallels; | |
| parallels = [+x[0], +x[1]]; | |
| return reload(); | |
| }; | |
| albers.scale = function(x) { | |
| if (!arguments.length) return scale; | |
| scale = +x; | |
| return albers; | |
| }; | |
| albers.translate = function(x) { | |
| if (!arguments.length) return translate; | |
| translate = [+x[0], +x[1]]; | |
| return albers; | |
| }; | |
| return reload(); | |
| }; | |
| // A composite projection for the United States, 960x500. The set of standard | |
| // parallels for each region comes from USGS, which is published here: | |
| // http://egsc.usgs.gov/isb/pubs/MapProjections/projections.html#albers | |
| // TODO allow the composite projection to be rescaled? | |
| d3.geo.albersUsa = function() { | |
| var lower48 = d3.geo.albers(); | |
| var alaska = d3.geo.albers() | |
| .origin([-160, 60]) | |
| .parallels([55, 65]); | |
| var hawaii = d3.geo.albers() | |
| .origin([-160, 20]) | |
| .parallels([8, 18]); | |
| var puertoRico = d3.geo.albers() | |
| .origin([-60, 10]) | |
| .parallels([8, 18]); | |
| function albersUsa(coordinates) { | |
| var lon = coordinates[0], | |
| lat = coordinates[1]; | |
| return (lat > 50 ? alaska | |
| : lon < -140 ? hawaii | |
| : lat < 21 ? puertoRico | |
| : lower48)(coordinates); | |
| } | |
| albersUsa.scale = function(x) { | |
| if (!arguments.length) return lower48.scale(); | |
| lower48.scale(x); | |
| alaska.scale(x * .6); | |
| hawaii.scale(x); | |
| puertoRico.scale(x * 1.5); | |
| return albersUsa.translate(lower48.translate()); | |
| }; | |
| albersUsa.translate = function(x) { | |
| if (!arguments.length) return lower48.translate(); | |
| var dz = lower48.scale() / 1000, | |
| dx = x[0], | |
| dy = x[1]; | |
| lower48.translate(x); | |
| alaska.translate([dx - 400 * dz, dy + 170 * dz]); | |
| hawaii.translate([dx - 190 * dz, dy + 200 * dz]); | |
| puertoRico.translate([dx + 580 * dz, dy + 430 * dz]); | |
| return albersUsa; | |
| }; | |
| return albersUsa.scale(lower48.scale()); | |
| }; | |
| d3.geo.bonne = function() { | |
| var scale = 200, | |
| translate = [480, 250], | |
| x0, // origin longitude in radians | |
| y0, // origin latitude in radians | |
| y1, // parallel latitude in radians | |
| c1; // cot(y1) | |
| function bonne(coordinates) { | |
| var x = coordinates[0] * d3_geo_radians - x0, | |
| y = coordinates[1] * d3_geo_radians - y0; | |
| if (y1) { | |
| var p = c1 + y1 - y, E = x * Math.cos(y) / p; | |
| x = p * Math.sin(E); | |
| y = p * Math.cos(E) - c1; | |
| } else { | |
| x *= Math.cos(y); | |
| y *= -1; | |
| } | |
| return [ | |
| scale * x + translate[0], | |
| scale * y + translate[1] | |
| ]; | |
| } | |
| bonne.invert = function(coordinates) { | |
| var x = (coordinates[0] - translate[0]) / scale, | |
| y = (coordinates[1] - translate[1]) / scale; | |
| if (y1) { | |
| var c = c1 + y, p = Math.sqrt(x * x + c * c); | |
| y = c1 + y1 - p; | |
| x = x0 + p * Math.atan2(x, c) / Math.cos(y); | |
| } else { | |
| y *= -1; | |
| x /= Math.cos(y); | |
| } | |
| return [ | |
| x / d3_geo_radians, | |
| y / d3_geo_radians | |
| ]; | |
| }; | |
| // 90° for Werner, 0° for Sinusoidal | |
| bonne.parallel = function(x) { | |
| if (!arguments.length) return y1 / d3_geo_radians; | |
| c1 = 1 / Math.tan(y1 = x * d3_geo_radians); | |
| return bonne; | |
| }; | |
| bonne.origin = function(x) { | |
| if (!arguments.length) return [x0 / d3_geo_radians, y0 / d3_geo_radians]; | |
| x0 = x[0] * d3_geo_radians; | |
| y0 = x[1] * d3_geo_radians; | |
| return bonne; | |
| }; | |
| bonne.scale = function(x) { | |
| if (!arguments.length) return scale; | |
| scale = +x; | |
| return bonne; | |
| }; | |
| bonne.translate = function(x) { | |
| if (!arguments.length) return translate; | |
| translate = [+x[0], +x[1]]; | |
| return bonne; | |
| }; | |
| return bonne.origin([0, 0]).parallel(45); | |
| }; | |
| d3.geo.equirectangular = function() { | |
| var scale = 500, | |
| translate = [480, 250]; | |
| function equirectangular(coordinates) { | |
| var x = coordinates[0] / 360, | |
| y = -coordinates[1] / 360; | |
| return [ | |
| scale * x + translate[0], | |
| scale * y + translate[1] | |
| ]; | |
| } | |
| equirectangular.invert = function(coordinates) { | |
| var x = (coordinates[0] - translate[0]) / scale, | |
| y = (coordinates[1] - translate[1]) / scale; | |
| return [ | |
| 360 * x, | |
| -360 * y | |
| ]; | |
| }; | |
| equirectangular.scale = function(x) { | |
| if (!arguments.length) return scale; | |
| scale = +x; | |
| return equirectangular; | |
| }; | |
| equirectangular.translate = function(x) { | |
| if (!arguments.length) return translate; | |
| translate = [+x[0], +x[1]]; | |
| return equirectangular; | |
| }; | |
| return equirectangular; | |
| }; | |
| d3.geo.mercator = function() { | |
| var scale = 500, | |
| translate = [480, 250]; | |
| function mercator(coordinates) { | |
| var x = coordinates[0] / 360, | |
| y = -(Math.log(Math.tan(Math.PI / 4 + coordinates[1] * d3_geo_radians / 2)) / d3_geo_radians) / 360; | |
| return [ | |
| scale * x + translate[0], | |
| scale * Math.max(-.5, Math.min(.5, y)) + translate[1] | |
| ]; | |
| } | |
| mercator.invert = function(coordinates) { | |
| var x = (coordinates[0] - translate[0]) / scale, | |
| y = (coordinates[1] - translate[1]) / scale; | |
| return [ | |
| 360 * x, | |
| 2 * Math.atan(Math.exp(-360 * y * d3_geo_radians)) / d3_geo_radians - 90 | |
| ]; | |
| }; | |
| mercator.scale = function(x) { | |
| if (!arguments.length) return scale; | |
| scale = +x; | |
| return mercator; | |
| }; | |
| mercator.translate = function(x) { | |
| if (!arguments.length) return translate; | |
| translate = [+x[0], +x[1]]; | |
| return mercator; | |
| }; | |
| return mercator; | |
| }; | |
| function d3_geo_type(types, defaultValue) { | |
| return function(object) { | |
| return object && types.hasOwnProperty(object.type) ? types[object.type](object) : defaultValue; | |
| }; | |
| } | |
| /** | |
| * Returns a function that, given a GeoJSON object (e.g., a feature), returns | |
| * the corresponding SVG path. The function can be customized by overriding the | |
| * projection. Point features are mapped to circles with a default radius of | |
| * 4.5px; the radius can be specified either as a constant or a function that | |
| * is evaluated per object. | |
| */ | |
| d3.geo.path = function() { | |
| var pointRadius = 4.5, | |
| pointCircle = d3_path_circle(pointRadius), | |
| projection = d3.geo.albersUsa(), | |
| buffer = []; | |
| function path(d, i) { | |
| if (typeof pointRadius === "function") pointCircle = d3_path_circle(pointRadius.apply(this, arguments)); | |
| pathType(d); | |
| var result = buffer.length ? buffer.join("") : null; | |
| buffer = []; | |
| return result; | |
| } | |
| function project(coordinates) { | |
| return projection(coordinates).join(","); | |
| } | |
| var pathType = d3_geo_type({ | |
| FeatureCollection: function(o) { | |
| var features = o.features, | |
| i = -1, // features.index | |
| n = features.length; | |
| while (++i < n) buffer.push(pathType(features[i].geometry)); | |
| }, | |
| Feature: function(o) { | |
| pathType(o.geometry); | |
| }, | |
| Point: function(o) { | |
| buffer.push("M", project(o.coordinates), pointCircle); | |
| }, | |
| MultiPoint: function(o) { | |
| var coordinates = o.coordinates, | |
| i = -1, // coordinates.index | |
| n = coordinates.length; | |
| while (++i < n) buffer.push("M", project(coordinates[i]), pointCircle); | |
| }, | |
| LineString: function(o) { | |
| var coordinates = o.coordinates, | |
| i = -1, // coordinates.index | |
| n = coordinates.length; | |
| buffer.push("M"); | |
| while (++i < n) buffer.push(project(coordinates[i]), "L"); | |
| buffer.pop(); | |
| }, | |
| MultiLineString: function(o) { | |
| var coordinates = o.coordinates, | |
| i = -1, // coordinates.index | |
| n = coordinates.length, | |
| subcoordinates, // coordinates[i] | |
| j, // subcoordinates.index | |
| m; // subcoordinates.length | |
| while (++i < n) { | |
| subcoordinates = coordinates[i]; | |
| j = -1; | |
| m = subcoordinates.length; | |
| buffer.push("M"); | |
| while (++j < m) buffer.push(project(subcoordinates[j]), "L"); | |
| buffer.pop(); | |
| } | |
| }, | |
| Polygon: function(o) { | |
| var coordinates = o.coordinates, | |
| i = -1, // coordinates.index | |
| n = coordinates.length, | |
| subcoordinates, // coordinates[i] | |
| j, // subcoordinates.index | |
| m; // subcoordinates.length | |
| while (++i < n) { | |
| subcoordinates = coordinates[i]; | |
| j = -1; | |
| if ((m = subcoordinates.length - 1) > 0) { | |
| buffer.push("M"); | |
| while (++j < m) buffer.push(project(subcoordinates[j]), "L"); | |
| buffer[buffer.length - 1] = "Z"; | |
| } | |
| } | |
| }, | |
| MultiPolygon: function(o) { | |
| var coordinates = o.coordinates, | |
| i = -1, // coordinates index | |
| n = coordinates.length, | |
| subcoordinates, // coordinates[i] | |
| j, // subcoordinates index | |
| m, // subcoordinates.length | |
| subsubcoordinates, // subcoordinates[j] | |
| k, // subsubcoordinates index | |
| p; // subsubcoordinates.length | |
| while (++i < n) { | |
| subcoordinates = coordinates[i]; | |
| j = -1; | |
| m = subcoordinates.length; | |
| while (++j < m) { | |
| subsubcoordinates = subcoordinates[j]; | |
| k = -1; | |
| if ((p = subsubcoordinates.length - 1) > 0) { | |
| buffer.push("M"); | |
| while (++k < p) buffer.push(project(subsubcoordinates[k]), "L"); | |
| buffer[buffer.length - 1] = "Z"; | |
| } | |
| } | |
| } | |
| }, | |
| GeometryCollection: function(o) { | |
| var geometries = o.geometries, | |
| i = -1, // geometries index | |
| n = geometries.length; | |
| while (++i < n) buffer.push(pathType(geometries[i])); | |
| } | |
| }); | |
| var areaType = path.area = d3_geo_type({ | |
| FeatureCollection: function(o) { | |
| var area = 0, | |
| features = o.features, | |
| i = -1, // features.index | |
| n = features.length; | |
| while (++i < n) area += areaType(features[i]); | |
| return area; | |
| }, | |
| Feature: function(o) { | |
| return areaType(o.geometry); | |
| }, | |
| Polygon: function(o) { | |
| return polygonArea(o.coordinates); | |
| }, | |
| MultiPolygon: function(o) { | |
| var sum = 0, | |
| coordinates = o.coordinates, | |
| i = -1, // coordinates index | |
| n = coordinates.length; | |
| while (++i < n) sum += polygonArea(coordinates[i]); | |
| return sum; | |
| }, | |
| GeometryCollection: function(o) { | |
| var sum = 0, | |
| geometries = o.geometries, | |
| i = -1, // geometries index | |
| n = geometries.length; | |
| while (++i < n) sum += areaType(geometries[i]); | |
| return sum; | |
| } | |
| }, 0); | |
| function polygonArea(coordinates) { | |
| var sum = area(coordinates[0]), // exterior ring | |
| i = 0, // coordinates.index | |
| n = coordinates.length; | |
| while (++i < n) sum -= area(coordinates[i]); // holes | |
| return sum; | |
| } | |
| function polygonCentroid(coordinates) { | |
| var polygon = d3.geom.polygon(coordinates[0].map(projection)), // exterior ring | |
| area = polygon.area(), | |
| centroid = polygon.centroid(area < 0 ? (area *= -1, 1) : -1), | |
| x = centroid[0], | |
| y = centroid[1], | |
| z = area, | |
| i = 0, // coordinates index | |
| n = coordinates.length; | |
| while (++i < n) { | |
| polygon = d3.geom.polygon(coordinates[i].map(projection)); // holes | |
| area = polygon.area(); | |
| centroid = polygon.centroid(area < 0 ? (area *= -1, 1) : -1); | |
| x -= centroid[0]; | |
| y -= centroid[1]; | |
| z -= area; | |
| } | |
| return [x, y, 6 * z]; // weighted centroid | |
| } | |
| var centroidType = path.centroid = d3_geo_type({ | |
| // TODO FeatureCollection | |
| // TODO Point | |
| // TODO MultiPoint | |
| // TODO LineString | |
| // TODO MultiLineString | |
| // TODO GeometryCollection | |
| Feature: function(o) { | |
| return centroidType(o.geometry); | |
| }, | |
| Polygon: function(o) { | |
| var centroid = polygonCentroid(o.coordinates); | |
| return [centroid[0] / centroid[2], centroid[1] / centroid[2]]; | |
| }, | |
| MultiPolygon: function(o) { | |
| var area = 0, | |
| coordinates = o.coordinates, | |
| centroid, | |
| x = 0, | |
| y = 0, | |
| z = 0, | |
| i = -1, // coordinates index | |
| n = coordinates.length; | |
| while (++i < n) { | |
| centroid = polygonCentroid(coordinates[i]); | |
| x += centroid[0]; | |
| y += centroid[1]; | |
| z += centroid[2]; | |
| } | |
| return [x / z, y / z]; | |
| } | |
| }); | |
| function area(coordinates) { | |
| return Math.abs(d3.geom.polygon(coordinates.map(projection)).area()); | |
| } | |
| path.projection = function(x) { | |
| projection = x; | |
| return path; | |
| }; | |
| path.pointRadius = function(x) { | |
| if (typeof x === "function") pointRadius = x; | |
| else { | |
| pointRadius = +x; | |
| pointCircle = d3_path_circle(pointRadius); | |
| } | |
| return path; | |
| }; | |
| return path; | |
| }; | |
| function d3_path_circle(radius) { | |
| return "m0," + radius | |
| + "a" + radius + "," + radius + " 0 1,1 0," + (-2 * radius) | |
| + "a" + radius + "," + radius + " 0 1,1 0," + (+2 * radius) | |
| + "z"; | |
| } | |
| /** | |
| * Given a GeoJSON object, returns the corresponding bounding box. The bounding | |
| * box is represented by a two-dimensional array: [[left, bottom], [right, | |
| * top]], where left is the minimum longitude, bottom is the minimum latitude, | |
| * right is maximum longitude, and top is the maximum latitude. | |
| */ | |
| d3.geo.bounds = function(feature) { | |
| var left = Infinity, | |
| bottom = Infinity, | |
| right = -Infinity, | |
| top = -Infinity; | |
| d3_geo_bounds(feature, function(x, y) { | |
| if (x < left) left = x; | |
| if (x > right) right = x; | |
| if (y < bottom) bottom = y; | |
| if (y > top) top = y; | |
| }); | |
| return [[left, bottom], [right, top]]; | |
| }; | |
| function d3_geo_bounds(o, f) { | |
| if (d3_geo_boundsTypes.hasOwnProperty(o.type)) d3_geo_boundsTypes[o.type](o, f); | |
| } | |
| var d3_geo_boundsTypes = { | |
| Feature: d3_geo_boundsFeature, | |
| FeatureCollection: d3_geo_boundsFeatureCollection, | |
| GeometryCollection: d3_geo_boundsGeometryCollection, | |
| LineString: d3_geo_boundsLineString, | |
| MultiLineString: d3_geo_boundsMultiLineString, | |
| MultiPoint: d3_geo_boundsLineString, | |
| MultiPolygon: d3_geo_boundsMultiPolygon, | |
| Point: d3_geo_boundsPoint, | |
| Polygon: d3_geo_boundsPolygon | |
| }; | |
| function d3_geo_boundsFeature(o, f) { | |
| d3_geo_bounds(o.geometry, f); | |
| } | |
| function d3_geo_boundsFeatureCollection(o, f) { | |
| for (var a = o.features, i = 0, n = a.length; i < n; i++) { | |
| d3_geo_bounds(a[i].geometry, f); | |
| } | |
| } | |
| function d3_geo_boundsGeometryCollection(o, f) { | |
| for (var a = o.geometries, i = 0, n = a.length; i < n; i++) { | |
| d3_geo_bounds(a[i], f); | |
| } | |
| } | |
| function d3_geo_boundsLineString(o, f) { | |
| for (var a = o.coordinates, i = 0, n = a.length; i < n; i++) { | |
| f.apply(null, a[i]); | |
| } | |
| } | |
| function d3_geo_boundsMultiLineString(o, f) { | |
| for (var a = o.coordinates, i = 0, n = a.length; i < n; i++) { | |
| for (var b = a[i], j = 0, m = b.length; j < m; j++) { | |
| f.apply(null, b[j]); | |
| } | |
| } | |
| } | |
| function d3_geo_boundsMultiPolygon(o, f) { | |
| for (var a = o.coordinates, i = 0, n = a.length; i < n; i++) { | |
| for (var b = a[i][0], j = 0, m = b.length; j < m; j++) { | |
| f.apply(null, b[j]); | |
| } | |
| } | |
| } | |
| function d3_geo_boundsPoint(o, f) { | |
| f.apply(null, o.coordinates); | |
| } | |
| function d3_geo_boundsPolygon(o, f) { | |
| for (var a = o.coordinates[0], i = 0, n = a.length; i < n; i++) { | |
| f.apply(null, a[i]); | |
| } | |
| } | |
| // TODO breakAtDateLine? | |
| d3.geo.circle = function() { | |
| var origin = [0, 0], | |
| degrees = 90 - 1e-2, | |
| radians = degrees * d3_geo_radians, | |
| arc = d3.geo.greatArc().source(origin).target(d3_identity); | |
| function circle() { | |
| // TODO render a circle as a Polygon | |
| } | |
| function visible(point) { | |
| return arc.distance(point) < radians; | |
| } | |
| circle.clip = function(d) { | |
| if (typeof origin === "function") arc.source(origin.apply(this, arguments)); | |
| return clipType(d) || null; | |
| }; | |
| var clipType = d3_geo_type({ | |
| FeatureCollection: function(o) { | |
| var features = o.features.map(clipType).filter(d3_identity); | |
| return features && (o = Object.create(o), o.features = features, o); | |
| }, | |
| Feature: function(o) { | |
| var geometry = clipType(o.geometry); | |
| return geometry && (o = Object.create(o), o.geometry = geometry, o); | |
| }, | |
| Point: function(o) { | |
| return visible(o.coordinates) && o; | |
| }, | |
| MultiPoint: function(o) { | |
| var coordinates = o.coordinates.filter(visible); | |
| return coordinates.length && { | |
| type: o.type, | |
| coordinates: coordinates | |
| }; | |
| }, | |
| LineString: function(o) { | |
| var coordinates = clip(o.coordinates); | |
| return coordinates.length && (o = Object.create(o), o.coordinates = coordinates, o); | |
| }, | |
| MultiLineString: function(o) { | |
| var coordinates = o.coordinates.map(clip).filter(function(d) { return d.length; }); | |
| return coordinates.length && (o = Object.create(o), o.coordinates = coordinates, o); | |
| }, | |
| Polygon: function(o) { | |
| var coordinates = o.coordinates.map(clip); | |
| return coordinates[0].length && (o = Object.create(o), o.coordinates = coordinates, o); | |
| }, | |
| MultiPolygon: function(o) { | |
| var coordinates = o.coordinates.map(function(d) { return d.map(clip); }).filter(function(d) { return d[0].length; }); | |
| return coordinates.length && (o = Object.create(o), o.coordinates = coordinates, o); | |
| }, | |
| GeometryCollection: function(o) { | |
| var geometries = o.geometries.map(clipType).filter(d3_identity); | |
| return geometries.length && (o = Object.create(o), o.geometries = geometries, o); | |
| } | |
| }); | |
| function clip(coordinates) { | |
| var i = -1, | |
| n = coordinates.length, | |
| clipped = [], | |
| p0, | |
| p1, | |
| p2, | |
| d0, | |
| d1; | |
| while (++i < n) { | |
| d1 = arc.distance(p2 = coordinates[i]); | |
| if (d1 < radians) { | |
| if (p1) clipped.push(d3_geo_greatArcInterpolate(p1, p2)((d0 - radians) / (d0 - d1))); | |
| clipped.push(p2); | |
| p0 = p1 = null; | |
| } else { | |
| p1 = p2; | |
| if (!p0 && clipped.length) { | |
| clipped.push(d3_geo_greatArcInterpolate(clipped[clipped.length - 1], p1)((radians - d0) / (d1 - d0))); | |
| p0 = p1; | |
| } | |
| } | |
| d0 = d1; | |
| } | |
| // Close the clipped polygon if necessary. | |
| p0 = coordinates[0]; | |
| p1 = clipped[0]; | |
| if (p1 && p2[0] === p0[0] && p2[1] === p0[1] && !(p2[0] === p1[0] && p2[1] === p1[1])) { | |
| clipped.push(p1); | |
| } | |
| return resample(clipped); | |
| } | |
| // Resample coordinates, creating great arcs between each. | |
| function resample(coordinates) { | |
| var i = 0, | |
| n = coordinates.length, | |
| j, | |
| m, | |
| resampled = n ? [coordinates[0]] : coordinates, | |
| resamples, | |
| origin = arc.source(); | |
| while (++i < n) { | |
| resamples = arc.source(coordinates[i - 1])(coordinates[i]).coordinates; | |
| for (j = 0, m = resamples.length; ++j < m;) resampled.push(resamples[j]); | |
| } | |
| arc.source(origin); | |
| return resampled; | |
| } | |
| circle.origin = function(x) { | |
| if (!arguments.length) return origin; | |
| origin = x; | |
| if (typeof origin !== "function") arc.source(origin); | |
| return circle; | |
| }; | |
| circle.angle = function(x) { | |
| if (!arguments.length) return degrees; | |
| radians = (degrees = +x) * d3_geo_radians; | |
| return circle; | |
| }; | |
| return d3.rebind(circle, arc, "precision"); | |
| } | |
| d3.geo.greatArc = function() { | |
| var source = d3_geo_greatArcSource, p0, | |
| target = d3_geo_greatArcTarget, p1, | |
| precision = 6 * d3_geo_radians, | |
| interpolate = d3_geo_greatArcInterpolator(); | |
| function greatArc() { | |
| var d = greatArc.distance.apply(this, arguments), // initializes the interpolator, too | |
| t = 0, | |
| dt = precision / d, | |
| coordinates = [p0]; | |
| while ((t += dt) < 1) coordinates.push(interpolate(t)); | |
| coordinates.push(p1); | |
| return {type: "LineString", coordinates: coordinates}; | |
| } | |
| // Length returned in radians; multiply by radius for distance. | |
| greatArc.distance = function() { | |
| if (typeof source === "function") interpolate.source(p0 = source.apply(this, arguments)); | |
| if (typeof target === "function") interpolate.target(p1 = target.apply(this, arguments)); | |
| return interpolate.distance(); | |
| }; | |
| greatArc.source = function(_) { | |
| if (!arguments.length) return source; | |
| source = _; | |
| if (typeof source !== "function") interpolate.source(p0 = source); | |
| return greatArc; | |
| }; | |
| greatArc.target = function(_) { | |
| if (!arguments.length) return target; | |
| target = _; | |
| if (typeof target !== "function") interpolate.target(p1 = target); | |
| return greatArc; | |
| }; | |
| // Precision is specified in degrees. | |
| greatArc.precision = function(_) { | |
| if (!arguments.length) return precision / d3_geo_radians; | |
| precision = _ * d3_geo_radians; | |
| return greatArc; | |
| }; | |
| return greatArc; | |
| }; | |
| function d3_geo_greatArcSource(d) { | |
| return d.source; | |
| } | |
| function d3_geo_greatArcTarget(d) { | |
| return d.target; | |
| } | |
| function d3_geo_greatArcInterpolator() { | |
| var x0, y0, cy0, sy0, kx0, ky0, | |
| x1, y1, cy1, sy1, kx1, ky1, | |
| d, | |
| k; | |
| function interpolate(t) { | |
| var B = Math.sin(t *= d) * k, | |
| A = Math.sin(d - t) * k, | |
| x = A * kx0 + B * kx1, | |
| y = A * ky0 + B * ky1, | |
| z = A * sy0 + B * sy1; | |
| return [ | |
| Math.atan2(y, x) / d3_geo_radians, | |
| Math.atan2(z, Math.sqrt(x * x + y * y)) / d3_geo_radians | |
| ]; | |
| } | |
| interpolate.distance = function() { | |
| if (d == null) k = 1 / Math.sin(d = Math.acos(Math.max(-1, Math.min(1, sy0 * sy1 + cy0 * cy1 * Math.cos(x1 - x0))))); | |
| return d; | |
| }; | |
| interpolate.source = function(_) { | |
| var cx0 = Math.cos(x0 = _[0] * d3_geo_radians), | |
| sx0 = Math.sin(x0); | |
| cy0 = Math.cos(y0 = _[1] * d3_geo_radians); | |
| sy0 = Math.sin(y0); | |
| kx0 = cy0 * cx0; | |
| ky0 = cy0 * sx0; | |
| d = null; | |
| return interpolate; | |
| }; | |
| interpolate.target = function(_) { | |
| var cx1 = Math.cos(x1 = _[0] * d3_geo_radians), | |
| sx1 = Math.sin(x1); | |
| cy1 = Math.cos(y1 = _[1] * d3_geo_radians); | |
| sy1 = Math.sin(y1); | |
| kx1 = cy1 * cx1; | |
| ky1 = cy1 * sx1; | |
| d = null; | |
| return interpolate; | |
| }; | |
| return interpolate; | |
| } | |
| function d3_geo_greatArcInterpolate(a, b) { | |
| var i = d3_geo_greatArcInterpolator().source(a).target(b); | |
| i.distance(); | |
| return i; | |
| } | |
| d3.geo.greatCircle = d3.geo.circle; | |
| d3.geom = {}; | |
| /** | |
| * Computes a contour for a given input grid function using the <a | |
| * href="http://en.wikipedia.org/wiki/Marching_squares">marching | |
| * squares</a> algorithm. Returns the contour polygon as an array of points. | |
| * | |
| * @param grid a two-input function(x, y) that returns true for values | |
| * inside the contour and false for values outside the contour. | |
| * @param start an optional starting point [x, y] on the grid. | |
| * @returns polygon [[x1, y1], [x2, y2], …] | |
| */ | |
| d3.geom.contour = function(grid, start) { | |
| var s = start || d3_geom_contourStart(grid), // starting point | |
| c = [], // contour polygon | |
| x = s[0], // current x position | |
| y = s[1], // current y position | |
| dx = 0, // next x direction | |
| dy = 0, // next y direction | |
| pdx = NaN, // previous x direction | |
| pdy = NaN, // previous y direction | |
| i = 0; | |
| do { | |
| // determine marching squares index | |
| i = 0; | |
| if (grid(x-1, y-1)) i += 1; | |
| if (grid(x, y-1)) i += 2; | |
| if (grid(x-1, y )) i += 4; | |
| if (grid(x, y )) i += 8; | |
| // determine next direction | |
| if (i === 6) { | |
| dx = pdy === -1 ? -1 : 1; | |
| dy = 0; | |
| } else if (i === 9) { | |
| dx = 0; | |
| dy = pdx === 1 ? -1 : 1; | |
| } else { | |
| dx = d3_geom_contourDx[i]; | |
| dy = d3_geom_contourDy[i]; | |
| } | |
| // update contour polygon | |
| if (dx != pdx && dy != pdy) { | |
| c.push([x, y]); | |
| pdx = dx; | |
| pdy = dy; | |
| } | |
| x += dx; | |
| y += dy; | |
| } while (s[0] != x || s[1] != y); | |
| return c; | |
| }; | |
| // lookup tables for marching directions | |
| var d3_geom_contourDx = [1, 0, 1, 1,-1, 0,-1, 1,0, 0,0,0,-1, 0,-1,NaN], | |
| d3_geom_contourDy = [0,-1, 0, 0, 0,-1, 0, 0,1,-1,1,1, 0,-1, 0,NaN]; | |
| function d3_geom_contourStart(grid) { | |
| var x = 0, | |
| y = 0; | |
| // search for a starting point; begin at origin | |
| // and proceed along outward-expanding diagonals | |
| while (true) { | |
| if (grid(x,y)) { | |
| return [x,y]; | |
| } | |
| if (x === 0) { | |
| x = y + 1; | |
| y = 0; | |
| } else { | |
| x = x - 1; | |
| y = y + 1; | |
| } | |
| } | |
| } | |
| /** | |
| * Computes the 2D convex hull of a set of points using Graham's scanning | |
| * algorithm. The algorithm has been implemented as described in Cormen, | |
| * Leiserson, and Rivest's Introduction to Algorithms. The running time of | |
| * this algorithm is O(n log n), where n is the number of input points. | |
| * | |
| * @param vertices [[x1, y1], [x2, y2], …] | |
| * @returns polygon [[x1, y1], [x2, y2], …] | |
| */ | |
| d3.geom.hull = function(vertices) { | |
| if (vertices.length < 3) return []; | |
| var len = vertices.length, | |
| plen = len - 1, | |
| points = [], | |
| stack = [], | |
| i, j, h = 0, x1, y1, x2, y2, u, v, a, sp; | |
| // find the starting ref point: leftmost point with the minimum y coord | |
| for (i=1; i<len; ++i) { | |
| if (vertices[i][1] < vertices[h][1]) { | |
| h = i; | |
| } else if (vertices[i][1] == vertices[h][1]) { | |
| h = (vertices[i][0] < vertices[h][0] ? i : h); | |
| } | |
| } | |
| // calculate polar angles from ref point and sort | |
| for (i=0; i<len; ++i) { | |
| if (i === h) continue; | |
| y1 = vertices[i][1] - vertices[h][1]; | |
| x1 = vertices[i][0] - vertices[h][0]; | |
| points.push({angle: Math.atan2(y1, x1), index: i}); | |
| } | |
| points.sort(function(a, b) { return a.angle - b.angle; }); | |
| // toss out duplicate angles | |
| a = points[0].angle; | |
| v = points[0].index; | |
| u = 0; | |
| for (i=1; i<plen; ++i) { | |
| j = points[i].index; | |
| if (a == points[i].angle) { | |
| // keep angle for point most distant from the reference | |
| x1 = vertices[v][0] - vertices[h][0]; | |
| y1 = vertices[v][1] - vertices[h][1]; | |
| x2 = vertices[j][0] - vertices[h][0]; | |
| y2 = vertices[j][1] - vertices[h][1]; | |
| if ((x1*x1 + y1*y1) >= (x2*x2 + y2*y2)) { | |
| points[i].index = -1; | |
| } else { | |
| points[u].index = -1; | |
| a = points[i].angle; | |
| u = i; | |
| v = j; | |
| } | |
| } else { | |
| a = points[i].angle; | |
| u = i; | |
| v = j; | |
| } | |
| } | |
| // initialize the stack | |
| stack.push(h); | |
| for (i=0, j=0; i<2; ++j) { | |
| if (points[j].index !== -1) { | |
| stack.push(points[j].index); | |
| i++; | |
| } | |
| } | |
| sp = stack.length; | |
| // do graham's scan | |
| for (; j<plen; ++j) { | |
| if (points[j].index === -1) continue; // skip tossed out points | |
| while (sp >= 2 && !d3_geom_hullCCW(stack[sp-2], stack[sp-1], points[j].index, vertices)) { | |
| --sp; | |
| } | |
| stack[sp++] = points[j].index; | |
| } | |
| // construct the hull | |
| var poly = []; | |
| for (i=0; i<sp; ++i) { | |
| poly.push(vertices[stack[i]]); | |
| } | |
| return poly; | |
| } | |
| // are three points in counter-clockwise order? | |
| function d3_geom_hullCCW(i1, i2, i3, v) { | |
| var t, a, b, c, d, e, f; | |
| t = v[i1]; a = t[0]; b = t[1]; | |
| t = v[i2]; c = t[0]; d = t[1]; | |
| t = v[i3]; e = t[0]; f = t[1]; | |
| return ((f-b)*(c-a) - (d-b)*(e-a)) > 0; | |
| } | |
| // Note: requires coordinates to be counterclockwise and convex! | |
| d3.geom.polygon = function(coordinates) { | |
| coordinates.area = function() { | |
| var i = 0, | |
| n = coordinates.length, | |
| a = coordinates[n - 1][0] * coordinates[0][1], | |
| b = coordinates[n - 1][1] * coordinates[0][0]; | |
| while (++i < n) { | |
| a += coordinates[i - 1][0] * coordinates[i][1]; | |
| b += coordinates[i - 1][1] * coordinates[i][0]; | |
| } | |
| return (b - a) * .5; | |
| }; | |
| coordinates.centroid = function(k) { | |
| var i = -1, | |
| n = coordinates.length, | |
| x = 0, | |
| y = 0, | |
| a, | |
| b = coordinates[n - 1], | |
| c; | |
| if (!arguments.length) k = -1 / (6 * coordinates.area()); | |
| while (++i < n) { | |
| a = b; | |
| b = coordinates[i]; | |
| c = a[0] * b[1] - b[0] * a[1]; | |
| x += (a[0] + b[0]) * c; | |
| y += (a[1] + b[1]) * c; | |
| } | |
| return [x * k, y * k]; | |
| }; | |
| // The Sutherland-Hodgman clipping algorithm. | |
| coordinates.clip = function(subject) { | |
| var input, | |
| i = -1, | |
| n = coordinates.length, | |
| j, | |
| m, | |
| a = coordinates[n - 1], | |
| b, | |
| c, | |
| d; | |
| while (++i < n) { | |
| input = subject.slice(); | |
| subject.length = 0; | |
| b = coordinates[i]; | |
| c = input[(m = input.length) - 1]; | |
| j = -1; | |
| while (++j < m) { | |
| d = input[j]; | |
| if (d3_geom_polygonInside(d, a, b)) { | |
| if (!d3_geom_polygonInside(c, a, b)) { | |
| subject.push(d3_geom_polygonIntersect(c, d, a, b)); | |
| } | |
| subject.push(d); | |
| } else if (d3_geom_polygonInside(c, a, b)) { | |
| subject.push(d3_geom_polygonIntersect(c, d, a, b)); | |
| } | |
| c = d; | |
| } | |
| a = b; | |
| } | |
| return subject; | |
| }; | |
| return coordinates; | |
| }; | |
| function d3_geom_polygonInside(p, a, b) { | |
| return (b[0] - a[0]) * (p[1] - a[1]) < (b[1] - a[1]) * (p[0] - a[0]); | |
| } | |
| // Intersect two infinite lines cd and ab. | |
| function d3_geom_polygonIntersect(c, d, a, b) { | |
| var x1 = c[0], x2 = d[0], x3 = a[0], x4 = b[0], | |
| y1 = c[1], y2 = d[1], y3 = a[1], y4 = b[1], | |
| x13 = x1 - x3, | |
| x21 = x2 - x1, | |
| x43 = x4 - x3, | |
| y13 = y1 - y3, | |
| y21 = y2 - y1, | |
| y43 = y4 - y3, | |
| ua = (x43 * y13 - y43 * x13) / (y43 * x21 - x43 * y21); | |
| return [x1 + ua * x21, y1 + ua * y21]; | |
| } | |
| // Adapted from Nicolas Garcia Belmonte's JIT implementation: | |
| // http://blog.thejit.org/2010/02/12/voronoi-tessellation/ | |
| // http://blog.thejit.org/assets/voronoijs/voronoi.js | |
| // See lib/jit/LICENSE for details. | |
| // Notes: | |
| // | |
| // This implementation does not clip the returned polygons, so if you want to | |
| // clip them to a particular shape you will need to do that either in SVG or by | |
| // post-processing with d3.geom.polygon's clip method. | |
| // | |
| // If any vertices are coincident or have NaN positions, the behavior of this | |
| // method is undefined. Most likely invalid polygons will be returned. You | |
| // should filter invalid points, and consolidate coincident points, before | |
| // computing the tessellation. | |
| /** | |
| * @param vertices [[x1, y1], [x2, y2], …] | |
| * @returns polygons [[[x1, y1], [x2, y2], …], …] | |
| */ | |
| d3.geom.voronoi = function(vertices) { | |
| var polygons = vertices.map(function() { return []; }); | |
| d3_voronoi_tessellate(vertices, function(e) { | |
| var s1, | |
| s2, | |
| x1, | |
| x2, | |
| y1, | |
| y2; | |
| if (e.a === 1 && e.b >= 0) { | |
| s1 = e.ep.r; | |
| s2 = e.ep.l; | |
| } else { | |
| s1 = e.ep.l; | |
| s2 = e.ep.r; | |
| } | |
| if (e.a === 1) { | |
| y1 = s1 ? s1.y : -1e6; | |
| x1 = e.c - e.b * y1; | |
| y2 = s2 ? s2.y : 1e6; | |
| x2 = e.c - e.b * y2; | |
| } else { | |
| x1 = s1 ? s1.x : -1e6; | |
| y1 = e.c - e.a * x1; | |
| x2 = s2 ? s2.x : 1e6; | |
| y2 = e.c - e.a * x2; | |
| } | |
| var v1 = [x1, y1], | |
| v2 = [x2, y2]; | |
| polygons[e.region.l.index].push(v1, v2); | |
| polygons[e.region.r.index].push(v1, v2); | |
| }); | |
| // Reconnect the polygon segments into counterclockwise loops. | |
| return polygons.map(function(polygon, i) { | |
| var cx = vertices[i][0], | |
| cy = vertices[i][1]; | |
| polygon.forEach(function(v) { | |
| v.angle = Math.atan2(v[0] - cx, v[1] - cy); | |
| }); | |
| return polygon.sort(function(a, b) { | |
| return a.angle - b.angle; | |
| }).filter(function(d, i) { | |
| return !i || (d.angle - polygon[i - 1].angle > 1e-10); | |
| }); | |
| }); | |
| }; | |
| var d3_voronoi_opposite = {"l": "r", "r": "l"}; | |
| function d3_voronoi_tessellate(vertices, callback) { | |
| var Sites = { | |
| list: vertices | |
| .map(function(v, i) { | |
| return { | |
| index: i, | |
| x: v[0], | |
| y: v[1] | |
| }; | |
| }) | |
| .sort(function(a, b) { | |
| return a.y < b.y ? -1 | |
| : a.y > b.y ? 1 | |
| : a.x < b.x ? -1 | |
| : a.x > b.x ? 1 | |
| : 0; | |
| }), | |
| bottomSite: null | |
| }; | |
| var EdgeList = { | |
| list: [], | |
| leftEnd: null, | |
| rightEnd: null, | |
| init: function() { | |
| EdgeList.leftEnd = EdgeList.createHalfEdge(null, "l"); | |
| EdgeList.rightEnd = EdgeList.createHalfEdge(null, "l"); | |
| EdgeList.leftEnd.r = EdgeList.rightEnd; | |
| EdgeList.rightEnd.l = EdgeList.leftEnd; | |
| EdgeList.list.unshift(EdgeList.leftEnd, EdgeList.rightEnd); | |
| }, | |
| createHalfEdge: function(edge, side) { | |
| return { | |
| edge: edge, | |
| side: side, | |
| vertex: null, | |
| "l": null, | |
| "r": null | |
| }; | |
| }, | |
| insert: function(lb, he) { | |
| he.l = lb; | |
| he.r = lb.r; | |
| lb.r.l = he; | |
| lb.r = he; | |
| }, | |
| leftBound: function(p) { | |
| var he = EdgeList.leftEnd; | |
| do { | |
| he = he.r; | |
| } while (he != EdgeList.rightEnd && Geom.rightOf(he, p)); | |
| he = he.l; | |
| return he; | |
| }, | |
| del: function(he) { | |
| he.l.r = he.r; | |
| he.r.l = he.l; | |
| he.edge = null; | |
| }, | |
| right: function(he) { | |
| return he.r; | |
| }, | |
| left: function(he) { | |
| return he.l; | |
| }, | |
| leftRegion: function(he) { | |
| return he.edge == null | |
| ? Sites.bottomSite | |
| : he.edge.region[he.side]; | |
| }, | |
| rightRegion: function(he) { | |
| return he.edge == null | |
| ? Sites.bottomSite | |
| : he.edge.region[d3_voronoi_opposite[he.side]]; | |
| } | |
| }; | |
| var Geom = { | |
| bisect: function(s1, s2) { | |
| var newEdge = { | |
| region: {"l": s1, "r": s2}, | |
| ep: {"l": null, "r": null} | |
| }; | |
| var dx = s2.x - s1.x, | |
| dy = s2.y - s1.y, | |
| adx = dx > 0 ? dx : -dx, | |
| ady = dy > 0 ? dy : -dy; | |
| newEdge.c = s1.x * dx + s1.y * dy | |
| + (dx * dx + dy * dy) * .5; | |
| if (adx > ady) { | |
| newEdge.a = 1; | |
| newEdge.b = dy / dx; | |
| newEdge.c /= dx; | |
| } else { | |
| newEdge.b = 1; | |
| newEdge.a = dx / dy; | |
| newEdge.c /= dy; | |
| } | |
| return newEdge; | |
| }, | |
| intersect: function(el1, el2) { | |
| var e1 = el1.edge, | |
| e2 = el2.edge; | |
| if (!e1 || !e2 || (e1.region.r == e2.region.r)) { | |
| return null; | |
| } | |
| var d = (e1.a * e2.b) - (e1.b * e2.a); | |
| if (Math.abs(d) < 1e-10) { | |
| return null; | |
| } | |
| var xint = (e1.c * e2.b - e2.c * e1.b) / d, | |
| yint = (e2.c * e1.a - e1.c * e2.a) / d, | |
| e1r = e1.region.r, | |
| e2r = e2.region.r, | |
| el, | |
| e; | |
| if ((e1r.y < e2r.y) || | |
| (e1r.y == e2r.y && e1r.x < e2r.x)) { | |
| el = el1; | |
| e = e1; | |
| } else { | |
| el = el2; | |
| e = e2; | |
| } | |
| var rightOfSite = (xint >= e.region.r.x); | |
| if ((rightOfSite && (el.side === "l")) || | |
| (!rightOfSite && (el.side === "r"))) { | |
| return null; | |
| } | |
| return { | |
| x: xint, | |
| y: yint | |
| }; | |
| }, | |
| rightOf: function(he, p) { | |
| var e = he.edge, | |
| topsite = e.region.r, | |
| rightOfSite = (p.x > topsite.x); | |
| if (rightOfSite && (he.side === "l")) { | |
| return 1; | |
| } | |
| if (!rightOfSite && (he.side === "r")) { | |
| return 0; | |
| } | |
| if (e.a === 1) { | |
| var dyp = p.y - topsite.y, | |
| dxp = p.x - topsite.x, | |
| fast = 0, | |
| above = 0; | |
| if ((!rightOfSite && (e.b < 0)) || | |
| (rightOfSite && (e.b >= 0))) { | |
| above = fast = (dyp >= e.b * dxp); | |
| } else { | |
| above = ((p.x + p.y * e.b) > e.c); | |
| if (e.b < 0) { | |
| above = !above; | |
| } | |
| if (!above) { | |
| fast = 1; | |
| } | |
| } | |
| if (!fast) { | |
| var dxs = topsite.x - e.region.l.x; | |
| above = (e.b * (dxp * dxp - dyp * dyp)) < | |
| (dxs * dyp * (1 + 2 * dxp / dxs + e.b * e.b)); | |
| if (e.b < 0) { | |
| above = !above; | |
| } | |
| } | |
| } else /* e.b == 1 */ { | |
| var yl = e.c - e.a * p.x, | |
| t1 = p.y - yl, | |
| t2 = p.x - topsite.x, | |
| t3 = yl - topsite.y; | |
| above = (t1 * t1) > (t2 * t2 + t3 * t3); | |
| } | |
| return he.side === "l" ? above : !above; | |
| }, | |
| endPoint: function(edge, side, site) { | |
| edge.ep[side] = site; | |
| if (!edge.ep[d3_voronoi_opposite[side]]) return; | |
| callback(edge); | |
| }, | |
| distance: function(s, t) { | |
| var dx = s.x - t.x, | |
| dy = s.y - t.y; | |
| return Math.sqrt(dx * dx + dy * dy); | |
| } | |
| }; | |
| var EventQueue = { | |
| list: [], | |
| insert: function(he, site, offset) { | |
| he.vertex = site; | |
| he.ystar = site.y + offset; | |
| for (var i=0, list=EventQueue.list, l=list.length; i<l; i++) { | |
| var next = list[i]; | |
| if (he.ystar > next.ystar || | |
| (he.ystar == next.ystar && | |
| site.x > next.vertex.x)) { | |
| continue; | |
| } else { | |
| break; | |
| } | |
| } | |
| list.splice(i, 0, he); | |
| }, | |
| del: function(he) { | |
| for (var i=0, ls=EventQueue.list, l=ls.length; i<l && (ls[i] != he); ++i) {} | |
| ls.splice(i, 1); | |
| }, | |
| empty: function() { return EventQueue.list.length === 0; }, | |
| nextEvent: function(he) { | |
| for (var i=0, ls=EventQueue.list, l=ls.length; i<l; ++i) { | |
| if (ls[i] == he) return ls[i+1]; | |
| } | |
| return null; | |
| }, | |
| min: function() { | |
| var elem = EventQueue.list[0]; | |
| return { | |
| x: elem.vertex.x, | |
| y: elem.ystar | |
| }; | |
| }, | |
| extractMin: function() { | |
| return EventQueue.list.shift(); | |
| } | |
| }; | |
| EdgeList.init(); | |
| Sites.bottomSite = Sites.list.shift(); | |
| var newSite = Sites.list.shift(), newIntStar; | |
| var lbnd, rbnd, llbnd, rrbnd, bisector; | |
| var bot, top, temp, p, v; | |
| var e, pm; | |
| while (true) { | |
| if (!EventQueue.empty()) { | |
| newIntStar = EventQueue.min(); | |
| } | |
| if (newSite && (EventQueue.empty() | |
| || newSite.y < newIntStar.y | |
| || (newSite.y == newIntStar.y | |
| && newSite.x < newIntStar.x))) { //new site is smallest | |
| lbnd = EdgeList.leftBound(newSite); | |
| rbnd = EdgeList.right(lbnd); | |
| bot = EdgeList.rightRegion(lbnd); | |
| e = Geom.bisect(bot, newSite); | |
| bisector = EdgeList.createHalfEdge(e, "l"); | |
| EdgeList.insert(lbnd, bisector); | |
| p = Geom.intersect(lbnd, bisector); | |
| if (p) { | |
| EventQueue.del(lbnd); | |
| EventQueue.insert(lbnd, p, Geom.distance(p, newSite)); | |
| } | |
| lbnd = bisector; | |
| bisector = EdgeList.createHalfEdge(e, "r"); | |
| EdgeList.insert(lbnd, bisector); | |
| p = Geom.intersect(bisector, rbnd); | |
| if (p) { | |
| EventQueue.insert(bisector, p, Geom.distance(p, newSite)); | |
| } | |
| newSite = Sites.list.shift(); | |
| } else if (!EventQueue.empty()) { //intersection is smallest | |
| lbnd = EventQueue.extractMin(); | |
| llbnd = EdgeList.left(lbnd); | |
| rbnd = EdgeList.right(lbnd); | |
| rrbnd = EdgeList.right(rbnd); | |
| bot = EdgeList.leftRegion(lbnd); | |
| top = EdgeList.rightRegion(rbnd); | |
| v = lbnd.vertex; | |
| Geom.endPoint(lbnd.edge, lbnd.side, v); | |
| Geom.endPoint(rbnd.edge, rbnd.side, v); | |
| EdgeList.del(lbnd); | |
| EventQueue.del(rbnd); | |
| EdgeList.del(rbnd); | |
| pm = "l"; | |
| if (bot.y > top.y) { | |
| temp = bot; | |
| bot = top; | |
| top = temp; | |
| pm = "r"; | |
| } | |
| e = Geom.bisect(bot, top); | |
| bisector = EdgeList.createHalfEdge(e, pm); | |
| EdgeList.insert(llbnd, bisector); | |
| Geom.endPoint(e, d3_voronoi_opposite[pm], v); | |
| p = Geom.intersect(llbnd, bisector); | |
| if (p) { | |
| EventQueue.del(llbnd); | |
| EventQueue.insert(llbnd, p, Geom.distance(p, bot)); | |
| } | |
| p = Geom.intersect(bisector, rrbnd); | |
| if (p) { | |
| EventQueue.insert(bisector, p, Geom.distance(p, bot)); | |
| } | |
| } else { | |
| break; | |
| } | |
| }//end while | |
| for (lbnd = EdgeList.right(EdgeList.leftEnd); | |
| lbnd != EdgeList.rightEnd; | |
| lbnd = EdgeList.right(lbnd)) { | |
| callback(lbnd.edge); | |
| } | |
| } | |
| /** | |
| * @param vertices [[x1, y1], [x2, y2], …] | |
| * @returns triangles [[[x1, y1], [x2, y2], [x3, y3]], …] | |
| */ | |
| d3.geom.delaunay = function(vertices) { | |
| var edges = vertices.map(function() { return []; }), | |
| triangles = []; | |
| // Use the Voronoi tessellation to determine Delaunay edges. | |
| d3_voronoi_tessellate(vertices, function(e) { | |
| edges[e.region.l.index].push(vertices[e.region.r.index]); | |
| }); | |
| // Reconnect the edges into counterclockwise triangles. | |
| edges.forEach(function(edge, i) { | |
| var v = vertices[i], | |
| cx = v[0], | |
| cy = v[1]; | |
| edge.forEach(function(v) { | |
| v.angle = Math.atan2(v[0] - cx, v[1] - cy); | |
| }); | |
| edge.sort(function(a, b) { | |
| return a.angle - b.angle; | |
| }); | |
| for (var j = 0, m = edge.length - 1; j < m; j++) { | |
| triangles.push([v, edge[j], edge[j + 1]]); | |
| } | |
| }); | |
| return triangles; | |
| }; | |
| // Constructs a new quadtree for the specified array of points. A quadtree is a | |
| // two-dimensional recursive spatial subdivision. This implementation uses | |
| // square partitions, dividing each square into four equally-sized squares. Each | |
| // point exists in a unique node; if multiple points are in the same position, | |
| // some points may be stored on internal nodes rather than leaf nodes. Quadtrees | |
| // can be used to accelerate various spatial operations, such as the Barnes-Hut | |
| // approximation for computing n-body forces, or collision detection. | |
| d3.geom.quadtree = function(points, x1, y1, x2, y2) { | |
| var p, | |
| i = -1, | |
| n = points.length; | |
| // Type conversion for deprecated API. | |
| if (n && isNaN(points[0].x)) points = points.map(d3_geom_quadtreePoint); | |
| // Allow bounds to be specified explicitly. | |
| if (arguments.length < 5) { | |
| if (arguments.length === 3) { | |
| y2 = x2 = y1; | |
| y1 = x1; | |
| } else { | |
| x1 = y1 = Infinity; | |
| x2 = y2 = -Infinity; | |
| // Compute bounds. | |
| while (++i < n) { | |
| p = points[i]; | |
| if (p.x < x1) x1 = p.x; | |
| if (p.y < y1) y1 = p.y; | |
| if (p.x > x2) x2 = p.x; | |
| if (p.y > y2) y2 = p.y; | |
| } | |
| // Squarify the bounds. | |
| var dx = x2 - x1, | |
| dy = y2 - y1; | |
| if (dx > dy) y2 = y1 + dx; | |
| else x2 = x1 + dy; | |
| } | |
| } | |
| // Recursively inserts the specified point p at the node n or one of its | |
| // descendants. The bounds are defined by [x1, x2] and [y1, y2]. | |
| function insert(n, p, x1, y1, x2, y2) { | |
| if (isNaN(p.x) || isNaN(p.y)) return; // ignore invalid points | |
| if (n.leaf) { | |
| var v = n.point; | |
| if (v) { | |
| // If the point at this leaf node is at the same position as the new | |
| // point we are adding, we leave the point associated with the | |
| // internal node while adding the new point to a child node. This | |
| // avoids infinite recursion. | |
| if ((Math.abs(v.x - p.x) + Math.abs(v.y - p.y)) < .01) { | |
| insertChild(n, p, x1, y1, x2, y2); | |
| } else { | |
| n.point = null; | |
| insertChild(n, v, x1, y1, x2, y2); | |
| insertChild(n, p, x1, y1, x2, y2); | |
| } | |
| } else { | |
| n.point = p; | |
| } | |
| } else { | |
| insertChild(n, p, x1, y1, x2, y2); | |
| } | |
| } | |
| // Recursively inserts the specified point p into a descendant of node n. The | |
| // bounds are defined by [x1, x2] and [y1, y2]. | |
| function insertChild(n, p, x1, y1, x2, y2) { | |
| // Compute the split point, and the quadrant in which to insert p. | |
| var sx = (x1 + x2) * .5, | |
| sy = (y1 + y2) * .5, | |
| right = p.x >= sx, | |
| bottom = p.y >= sy, | |
| i = (bottom << 1) + right; | |
| // Recursively insert into the child node. | |
| n.leaf = false; | |
| n = n.nodes[i] || (n.nodes[i] = d3_geom_quadtreeNode(n)); | |
| // Update the bounds as we recurse. | |
| if (right) x1 = sx; else x2 = sx; | |
| if (bottom) y1 = sy; else y2 = sy; | |
| insert(n, p, x1, y1, x2, y2); | |
| } | |
| // Create the root node. | |
| var root = d3_geom_quadtreeNode(null); | |
| root.add = function(p) { | |
| insert(root, p, x1, y1, x2, y2); | |
| }; | |
| root.visit = function(f) { | |
| d3_geom_quadtreeVisit(f, root, x1, y1, x2, y2); | |
| }; | |
| // Insert all points. | |
| points.forEach(root.add); | |
| return root; | |
| }; | |
| function d3_geom_quadtreeNode(parent) { | |
| return { | |
| leaf: true, | |
| nodes: [], | |
| point: null, | |
| parent: parent | |
| }; | |
| } | |
| function d3_geom_quadtreeVisit(f, node, x1, y1, x2, y2) { | |
| if (!f(node, x1, y1, x2, y2)) { | |
| var sx = (x1 + x2) * .5, | |
| sy = (y1 + y2) * .5, | |
| children = node.nodes; | |
| if (children[0]) d3_geom_quadtreeVisit(f, children[0], x1, y1, sx, sy); | |
| if (children[1]) d3_geom_quadtreeVisit(f, children[1], sx, y1, x2, sy); | |
| if (children[2]) d3_geom_quadtreeVisit(f, children[2], x1, sy, sx, y2); | |
| if (children[3]) d3_geom_quadtreeVisit(f, children[3], sx, sy, x2, y2); | |
| } | |
| } | |
| function d3_geom_quadtreePoint(p) { | |
| return { | |
| x: p[0], | |
| y: p[1] | |
| }; | |
| } | |
| d3.time = {}; | |
| var d3_time = Date, | |
| d3_time_daySymbols = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; | |
| function d3_time_utc() { | |
| this._ = new Date(arguments.length > 1 | |
| ? Date.UTC.apply(this, arguments) | |
| : arguments[0]); | |
| } | |
| d3_time_utc.prototype = { | |
| getDate: function() { return this._.getUTCDate(); }, | |
| getDay: function() { return this._.getUTCDay(); }, | |
| getFullYear: function() { return this._.getUTCFullYear(); }, | |
| getHours: function() { return this._.getUTCHours(); }, | |
| getMilliseconds: function() { return this._.getUTCMilliseconds(); }, | |
| getMinutes: function() { return this._.getUTCMinutes(); }, | |
| getMonth: function() { return this._.getUTCMonth(); }, | |
| getSeconds: function() { return this._.getUTCSeconds(); }, | |
| getTime: function() { return this._.getTime(); }, | |
| getTimezoneOffset: function() { return 0; }, | |
| valueOf: function() { return this._.valueOf(); }, | |
| setDate: function() { d3_time_prototype.setUTCDate.apply(this._, arguments); }, | |
| setDay: function() { d3_time_prototype.setUTCDay.apply(this._, arguments); }, | |
| setFullYear: function() { d3_time_prototype.setUTCFullYear.apply(this._, arguments); }, | |
| setHours: function() { d3_time_prototype.setUTCHours.apply(this._, arguments); }, | |
| setMilliseconds: function() { d3_time_prototype.setUTCMilliseconds.apply(this._, arguments); }, | |
| setMinutes: function() { d3_time_prototype.setUTCMinutes.apply(this._, arguments); }, | |
| setMonth: function() { d3_time_prototype.setUTCMonth.apply(this._, arguments); }, | |
| setSeconds: function() { d3_time_prototype.setUTCSeconds.apply(this._, arguments); }, | |
| setTime: function() { d3_time_prototype.setTime.apply(this._, arguments); } | |
| }; | |
| var d3_time_prototype = Date.prototype; | |
| // The date and time format (%c), date format (%x) and time format (%X). | |
| var d3_time_formatDateTime = "%a %b %e %H:%M:%S %Y", | |
| d3_time_formatDate = "%m/%d/%y", | |
| d3_time_formatTime = "%H:%M:%S"; | |
| // The weekday and month names. | |
| var d3_time_days = d3_time_daySymbols, | |
| d3_time_dayAbbreviations = d3_time_days.map(d3_time_formatAbbreviate), | |
| d3_time_months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"], | |
| d3_time_monthAbbreviations = d3_time_months.map(d3_time_formatAbbreviate); | |
| function d3_time_formatAbbreviate(name) { | |
| return name.substring(0, 3); | |
| } | |
| d3.time.format = function(template) { | |
| var n = template.length; | |
| function format(date) { | |
| var string = [], | |
| i = -1, | |
| j = 0, | |
| c, | |
| f; | |
| while (++i < n) { | |
| if (template.charCodeAt(i) == 37) { | |
| string.push( | |
| template.substring(j, i), | |
| (f = d3_time_formats[c = template.charAt(++i)]) | |
| ? f(date) : c); | |
| j = i + 1; | |
| } | |
| } | |
| string.push(template.substring(j, i)); | |
| return string.join(""); | |
| } | |
| format.parse = function(string) { | |
| var d = {y: 1900, m: 0, d: 1, H: 0, M: 0, S: 0, L: 0}, | |
| i = d3_time_parse(d, template, string, 0); | |
| if (i != string.length) return null; | |
| // 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); | |
| date.setHours(d.H, d.M, d.S, d.L); | |
| return date; | |
| }; | |
| format.toString = function() { | |
| return template; | |
| }; | |
| return format; | |
| }; | |
| function d3_time_parse(date, template, string, j) { | |
| var c, | |
| p, | |
| i = 0, | |
| n = template.length, | |
| m = string.length; | |
| while (i < n) { | |
| if (j >= m) return -1; | |
| c = template.charCodeAt(i++); | |
| if (c == 37) { | |
| p = d3_time_parsers[template.charAt(i++)]; | |
| if (!p || ((j = p(date, string, j)) < 0)) return -1; | |
| } else if (c != string.charCodeAt(j++)) { | |
| return -1; | |
| } | |
| } | |
| return j; | |
| } | |
| function d3_time_formatRe(names) { | |
| return new RegExp("^(?:" + names.map(d3.requote).join("|") + ")", "i"); | |
| } | |
| function d3_time_formatLookup(names) { | |
| var map = new d3_Map, i = -1, n = names.length; | |
| while (++i < n) map.set(names[i].toLowerCase(), i); | |
| return map; | |
| } | |
| var d3_time_zfill2 = d3.format("02d"), | |
| d3_time_zfill3 = d3.format("03d"), | |
| d3_time_zfill4 = d3.format("04d"), | |
| d3_time_sfill2 = d3.format("2d"); | |
| function d3_time_ordinal_suffix(number) { | |
| var suffix = "th", | |
| tail = number % 100; | |
| if (tail < 11 || tail > 13) { | |
| switch (tail % 10) { | |
| case 1: suffix = "st"; break; | |
| case 2: suffix = "nd"; break; | |
| case 3: suffix = "rd"; break; | |
| default: break; | |
| } | |
| } | |
| return suffix; | |
| } | |
| var d3_time_dayRe = d3_time_formatRe(d3_time_days), | |
| d3_time_dayAbbrevRe = d3_time_formatRe(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); | |
| var d3_time_formats = { | |
| a: function(d) { return d3_time_dayAbbreviations[d.getDay()]; }, | |
| A: function(d) { return d3_time_days[d.getDay()]; }, | |
| b: function(d) { return d3_time_monthAbbreviations[d.getMonth()]; }, | |
| B: function(d) { return d3_time_months[d.getMonth()]; }, | |
| c: d3.time.format(d3_time_formatDateTime), | |
| d: function(d) { return d3_time_zfill2(d.getDate()); }, | |
| e: function(d) { return d3_time_sfill2(d.getDate()); }, | |
| H: function(d) { return d3_time_zfill2(d.getHours()); }, | |
| I: function(d) { return d3_time_zfill2(d.getHours() % 12 || 12); }, | |
| j: function(d) { return d3_time_zfill3(1 + d3.time.dayOfYear(d)); }, | |
| L: function(d) { return d3_time_zfill3(d.getMilliseconds()); }, | |
| m: function(d) { return d3_time_zfill2(d.getMonth() + 1); }, | |
| M: function(d) { return d3_time_zfill2(d.getMinutes()); }, | |
| p: function(d) { return d.getHours() >= 12 ? "PM" : "AM"; }, | |
| s: function(d) { return d3_time_ordinal_suffix(d.getDate()); }, | |
| S: function(d) { return d3_time_zfill2(d.getSeconds()); }, | |
| U: function(d) { return d3_time_zfill2(d3.time.sundayOfYear(d)); }, | |
| w: function(d) { return d.getDay(); }, | |
| W: function(d) { return d3_time_zfill2(d3.time.mondayOfYear(d)); }, | |
| x: d3.time.format(d3_time_formatDate), | |
| X: d3.time.format(d3_time_formatTime), | |
| y: function(d) { return d3_time_zfill2(d.getFullYear() % 100); }, | |
| Y: function(d) { return d3_time_zfill4(d.getFullYear() % 10000); }, | |
| Z: d3_time_zone, | |
| "%": function(d) { return "%"; } | |
| }; | |
| var d3_time_parsers = { | |
| a: d3_time_parseWeekdayAbbrev, | |
| A: d3_time_parseWeekday, | |
| b: d3_time_parseMonthAbbrev, | |
| B: d3_time_parseMonth, | |
| c: d3_time_parseLocaleFull, | |
| d: d3_time_parseDay, | |
| 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; }, | |
| 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; }, | |
| x: d3_time_parseLocaleDate, | |
| X: d3_time_parseLocaleTime, | |
| y: d3_time_parseYear, | |
| Y: d3_time_parseFullYear | |
| // , | |
| // Z: function(d, s, i) { /*TODO time zone */ return i; }, | |
| // "%": function(d, s, i) { /*TODO literal % */ return i; } | |
| }; | |
| // 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; | |
| } | |
| // 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; | |
| } | |
| 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; | |
| } | |
| 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; | |
| } | |
| function d3_time_parseLocaleFull(date, string, i) { | |
| return d3_time_parse(date, d3_time_formats.c.toString(), string, i); | |
| } | |
| function d3_time_parseLocaleDate(date, string, i) { | |
| return d3_time_parse(date, d3_time_formats.x.toString(), string, i); | |
| } | |
| function d3_time_parseLocaleTime(date, string, i) { | |
| return d3_time_parse(date, d3_time_formats.X.toString(), 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; | |
| } | |
| 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; | |
| } | |
| function d3_time_expandYear(d) { | |
| // convert to 4-digit year according to POSIX/ISO rules (strptime) ~ http://docs.python.org/py3k/library/time.html | |
| return d + (((d >= 69) && (d < 100)) ? 1900 : 2000); | |
| } | |
| 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; | |
| } | |
| 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; | |
| } | |
| // 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; | |
| } | |
| 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; | |
| } | |
| 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; | |
| } | |
| 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; | |
| } | |
| // Note: we don't look at the next directive. | |
| var d3_time_numberRe = /^\s*\d+/; | |
| function d3_time_parseAmPm(date, string, i) { | |
| var n = d3_time_amPmLookup.get(string.substring(i, i += 2).toLowerCase()); | |
| return n == null ? -1 : (date.p = n, i); | |
| } | |
| var d3_time_amPmLookup = d3.map({ | |
| am: 0, | |
| pm: 1 | |
| }); | |
| // TODO table of time zone offset names? | |
| function d3_time_zone(d) { | |
| var z = d.getTimezoneOffset(), | |
| zs = z > 0 ? "-" : "+", | |
| zh = ~~(Math.abs(z) / 60), | |
| zm = Math.abs(z) % 60; | |
| return zs + d3_time_zfill2(zh) + d3_time_zfill2(zm); | |
| } | |
| d3.time.format.utc = function(template) { | |
| var local = d3.time.format(template); | |
| function format(date) { | |
| try { | |
| d3_time = d3_time_utc; | |
| var utc = new d3_time(); | |
| utc._ = date; | |
| return local(utc); | |
| } finally { | |
| d3_time = Date; | |
| } | |
| } | |
| format.parse = function(string) { | |
| try { | |
| d3_time = d3_time_utc; | |
| var date = local.parse(string); | |
| return date && date._; | |
| } finally { | |
| d3_time = Date; | |
| } | |
| }; | |
| format.toString = local.toString; | |
| return format; | |
| }; | |
| var d3_time_formatIso = d3.time.format.utc("%Y-%m-%dT%H:%M:%S.%LZ"); | |
| d3.time.format.iso = Date.prototype.toISOString ? d3_time_formatIsoNative : d3_time_formatIso; | |
| function d3_time_formatIsoNative(date) { | |
| return date.toISOString(); | |
| } | |
| d3_time_formatIsoNative.parse = function(string) { | |
| var date = new Date(string); | |
| return isNaN(date) ? null : date; | |
| }; | |
| d3_time_formatIsoNative.toString = d3_time_formatIso.toString; | |
| function d3_time_interval(local, step, number) { | |
| function round(date) { | |
| var d0 = local(date), d1 = offset(d0, 1); | |
| return date - d0 < d1 - date ? d0 : d1; | |
| } | |
| function ceil(date) { | |
| step(date = local(new d3_time(date - 1)), 1); | |
| return date; | |
| } | |
| function offset(date, k) { | |
| step(date = new d3_time(+date), k); | |
| return date; | |
| } | |
| function range(t0, t1, dt) { | |
| var time = ceil(t0), times = []; | |
| if (dt > 1) { | |
| while (time < t1) { | |
| if (!(number(time) % dt)) times.push(new Date(+time)); | |
| step(time, 1); | |
| } | |
| } else { | |
| while (time < t1) times.push(new Date(+time)), step(time, 1); | |
| } | |
| return times; | |
| } | |
| function range_utc(t0, t1, dt) { | |
| try { | |
| d3_time = d3_time_utc; | |
| var utc = new d3_time_utc(); | |
| utc._ = t0; | |
| return range(utc, t1, dt); | |
| } finally { | |
| d3_time = Date; | |
| } | |
| } | |
| local.floor = local; | |
| local.round = round; | |
| local.ceil = ceil; | |
| local.offset = offset; | |
| local.range = range; | |
| var utc = local.utc = d3_time_interval_utc(local); | |
| utc.floor = utc; | |
| utc.round = d3_time_interval_utc(round); | |
| utc.ceil = d3_time_interval_utc(ceil); | |
| utc.offset = d3_time_interval_utc(offset); | |
| utc.range = range_utc; | |
| return local; | |
| } | |
| function d3_time_interval_utc(method) { | |
| return function(date, k) { | |
| try { | |
| d3_time = d3_time_utc; | |
| var utc = new d3_time_utc(); | |
| utc._ = date; | |
| return method(utc, k)._; | |
| } finally { | |
| d3_time = Date; | |
| } | |
| }; | |
| } | |
| d3.time.second = d3_time_interval(function(date) { | |
| return new d3_time(Math.floor(date / 1e3) * 1e3); | |
| }, function(date, offset) { | |
| date.setTime(date.getTime() + Math.floor(offset) * 1e3); // DST breaks setSeconds | |
| }, function(date) { | |
| return date.getSeconds(); | |
| }); | |
| d3.time.seconds = d3.time.second.range; | |
| d3.time.seconds.utc = d3.time.second.utc.range; | |
| d3.time.minute = d3_time_interval(function(date) { | |
| return new d3_time(Math.floor(date / 6e4) * 6e4); | |
| }, function(date, offset) { | |
| date.setTime(date.getTime() + Math.floor(offset) * 6e4); // DST breaks setMinutes | |
| }, function(date) { | |
| return date.getMinutes(); | |
| }); | |
| d3.time.minutes = d3.time.minute.range; | |
| d3.time.minutes.utc = d3.time.minute.utc.range; | |
| d3.time.hour = d3_time_interval(function(date) { | |
| var timezone = date.getTimezoneOffset() / 60; | |
| return new d3_time((Math.floor(date / 36e5 - timezone) + timezone) * 36e5); | |
| }, function(date, offset) { | |
| date.setTime(date.getTime() + Math.floor(offset) * 36e5); // DST breaks setHours | |
| }, function(date) { | |
| return date.getHours(); | |
| }); | |
| d3.time.hours = d3.time.hour.range; | |
| d3.time.hours.utc = d3.time.hour.utc.range; | |
| d3.time.day = d3_time_interval(function(date) { | |
| var day = new d3_time(1970, 0); | |
| day.setFullYear(date.getFullYear(), date.getMonth(), date.getDate()); | |
| return day; | |
| }, function(date, offset) { | |
| date.setDate(date.getDate() + offset); | |
| }, function(date) { | |
| return date.getDate() - 1; | |
| }); | |
| d3.time.days = d3.time.day.range; | |
| d3.time.days.utc = d3.time.day.utc.range; | |
| d3.time.dayOfYear = function(date) { | |
| var year = d3.time.year(date); | |
| return Math.floor((date - year - (date.getTimezoneOffset() - year.getTimezoneOffset()) * 6e4) / 864e5); | |
| }; | |
| d3_time_daySymbols.forEach(function(day, i) { | |
| day = day.toLowerCase(); | |
| i = 7 - i; | |
| var interval = d3.time[day] = d3_time_interval(function(date) { | |
| (date = d3.time.day(date)).setDate(date.getDate() - (date.getDay() + i) % 7); | |
| return date; | |
| }, function(date, offset) { | |
| date.setDate(date.getDate() + Math.floor(offset) * 7); | |
| }, function(date) { | |
| var day = d3.time.year(date).getDay(); | |
| return Math.floor((d3.time.dayOfYear(date) + (day + i) % 7) / 7) - (day !== i); | |
| }); | |
| d3.time[day + "s"] = interval.range; | |
| d3.time[day + "s"].utc = interval.utc.range; | |
| d3.time[day + "OfYear"] = function(date) { | |
| var day = d3.time.year(date).getDay(); | |
| return Math.floor((d3.time.dayOfYear(date) + (day + i) % 7) / 7); | |
| }; | |
| }); | |
| d3.time.week = d3.time.sunday; | |
| d3.time.weeks = d3.time.sunday.range; | |
| d3.time.weeks.utc = d3.time.sunday.utc.range; | |
| d3.time.weekOfYear = d3.time.sundayOfYear; | |
| d3.time.month = d3_time_interval(function(date) { | |
| date = d3.time.day(date); | |
| date.setDate(1); | |
| return date; | |
| }, function(date, offset) { | |
| date.setMonth(date.getMonth() + offset); | |
| }, function(date) { | |
| return date.getMonth(); | |
| }); | |
| d3.time.months = d3.time.month.range; | |
| d3.time.months.utc = d3.time.month.utc.range; | |
| d3.time.year = d3_time_interval(function(date) { | |
| date = d3.time.day(date); | |
| date.setMonth(0, 1); | |
| return date; | |
| }, function(date, offset) { | |
| date.setFullYear(date.getFullYear() + offset); | |
| }, function(date) { | |
| return date.getFullYear(); | |
| }); | |
| d3.time.years = d3.time.year.range; | |
| d3.time.years.utc = d3.time.year.utc.range; | |
| function d3_time_scale(linear, methods, format) { | |
| function scale(x) { | |
| return linear(x); | |
| } | |
| scale.invert = function(x) { | |
| return d3_time_scaleDate(linear.invert(x)); | |
| }; | |
| scale.domain = function(x) { | |
| if (!arguments.length) return linear.domain().map(d3_time_scaleDate); | |
| linear.domain(x); | |
| return scale; | |
| }; | |
| scale.nice = function(m) { | |
| return scale.domain(d3_scale_nice(scale.domain(), function() { return m; })); | |
| }; | |
| scale.ticks = function(m, k) { | |
| var extent = d3_time_scaleExtent(scale.domain()); | |
| if (typeof m !== "function") { | |
| var span = extent[1] - extent[0], | |
| target = span / m, | |
| i = d3.bisect(d3_time_scaleSteps, target); | |
| if (i == d3_time_scaleSteps.length) return methods.year(extent, m); | |
| if (!i) return linear.ticks(m).map(d3_time_scaleDate); | |
| if (Math.log(target / d3_time_scaleSteps[i - 1]) < Math.log(d3_time_scaleSteps[i] / target)) --i; | |
| m = methods[i]; | |
| k = m[1]; | |
| m = m[0].range; | |
| } | |
| return m(extent[0], new Date(+extent[1] + 1), k); // inclusive upper bound | |
| }; | |
| scale.tickFormat = function() { | |
| return format; | |
| }; | |
| scale.copy = function() { | |
| return d3_time_scale(linear.copy(), methods, format); | |
| }; | |
| // TOOD expose d3_scale_linear_rebind? | |
| return d3.rebind(scale, linear, "range", "rangeRound", "interpolate", "clamp"); | |
| } | |
| // TODO expose d3_scaleExtent? | |
| function d3_time_scaleExtent(domain) { | |
| var start = domain[0], stop = domain[domain.length - 1]; | |
| return start < stop ? [start, stop] : [stop, start]; | |
| } | |
| function d3_time_scaleDate(t) { | |
| return new Date(t); | |
| } | |
| function d3_time_scaleFormat(formats) { | |
| return function(date) { | |
| var i = formats.length - 1, f = formats[i]; | |
| while (!f[1](date)) f = formats[--i]; | |
| return f[0](date); | |
| }; | |
| } | |
| function d3_time_scaleSetYear(y) { | |
| var d = new Date(y, 0, 1); | |
| d.setFullYear(y); // Y2K fail | |
| return d; | |
| } | |
| function d3_time_scaleGetYear(d) { | |
| var y = d.getFullYear(), | |
| d0 = d3_time_scaleSetYear(y), | |
| d1 = d3_time_scaleSetYear(y + 1); | |
| return y + (d - d0) / (d1 - d0); | |
| } | |
| var d3_time_scaleSteps = [ | |
| 1e3, // 1-second | |
| 5e3, // 5-second | |
| 15e3, // 15-second | |
| 3e4, // 30-second | |
| 6e4, // 1-minute | |
| 3e5, // 5-minute | |
| 9e5, // 15-minute | |
| 18e5, // 30-minute | |
| 36e5, // 1-hour | |
| 108e5, // 3-hour | |
| 216e5, // 6-hour | |
| 432e5, // 12-hour | |
| 864e5, // 1-day | |
| 1728e5, // 2-day | |
| 6048e5, // 1-week | |
| 2592e6, // 1-month | |
| 7776e6, // 3-month | |
| 31536e6 // 1-year | |
| ]; | |
| var d3_time_scaleLocalMethods = [ | |
| [d3.time.second, 1], | |
| [d3.time.second, 5], | |
| [d3.time.second, 15], | |
| [d3.time.second, 30], | |
| [d3.time.minute, 1], | |
| [d3.time.minute, 5], | |
| [d3.time.minute, 15], | |
| [d3.time.minute, 30], | |
| [d3.time.hour, 1], | |
| [d3.time.hour, 3], | |
| [d3.time.hour, 6], | |
| [d3.time.hour, 12], | |
| [d3.time.day, 1], | |
| [d3.time.day, 2], | |
| [d3.time.week, 1], | |
| [d3.time.month, 1], | |
| [d3.time.month, 3], | |
| [d3.time.year, 1] | |
| ]; | |
| var d3_time_scaleLocalFormats = [ | |
| [d3.time.format("%Y"), function(d) { return true; }], | |
| [d3.time.format("%B"), function(d) { return d.getMonth(); }], | |
| [d3.time.format("%b %d"), function(d) { return d.getDate() != 1; }], | |
| [d3.time.format("%a %d"), function(d) { return d.getDay() && d.getDate() != 1; }], | |
| [d3.time.format("%H"), function(d) { return d.getHours(); }], // [abh] Make the "auto-scaling" time formats use 24-hour hours instead of 12-hours and AM/PM | |
| [d3.time.format("%I:%M"), function(d) { return d.getMinutes(); }], | |
| [d3.time.format(":%S"), function(d) { return d.getSeconds(); }], | |
| [d3.time.format(".%L"), function(d) { return d.getMilliseconds(); }] | |
| ]; | |
| var d3_time_scaleLinear = d3.scale.linear(), | |
| d3_time_scaleLocalFormat = d3_time_scaleFormat(d3_time_scaleLocalFormats); | |
| d3_time_scaleLocalMethods.year = function(extent, m) { | |
| return d3_time_scaleLinear.domain(extent.map(d3_time_scaleGetYear)).ticks(m).map(d3_time_scaleSetYear); | |
| }; | |
| d3.time.scale = function() { | |
| return d3_time_scale(d3.scale.linear(), d3_time_scaleLocalMethods, d3_time_scaleLocalFormat); | |
| }; | |
| var d3_time_scaleUTCMethods = d3_time_scaleLocalMethods.map(function(m) { | |
| return [m[0].utc, m[1]]; | |
| }); | |
| var d3_time_scaleUTCFormats = [ | |
| [d3.time.format.utc("%Y"), function(d) { return true; }], | |
| [d3.time.format.utc("%B"), function(d) { return d.getUTCMonth(); }], | |
| [d3.time.format.utc("%b %d"), function(d) { return d.getUTCDate() != 1; }], | |
| [d3.time.format.utc("%a %d"), function(d) { return d.getUTCDay() && d.getUTCDate() != 1; }], | |
| [d3.time.format.utc("%H"), function(d) { return d.getUTCHours(); }], // [abh] Make the "auto-scaling" time formats use 24-hour hours instead of 12-hours and AM/PM | |
| [d3.time.format.utc("%I:%M"), function(d) { return d.getUTCMinutes(); }], | |
| [d3.time.format.utc(":%S"), function(d) { return d.getUTCSeconds(); }], | |
| [d3.time.format.utc(".%L"), function(d) { return d.getUTCMilliseconds(); }] | |
| ]; | |
| var d3_time_scaleUTCFormat = d3_time_scaleFormat(d3_time_scaleUTCFormats); | |
| function d3_time_scaleUTCSetYear(y) { | |
| var d = new Date(Date.UTC(y, 0, 1)); | |
| d.setUTCFullYear(y); // Y2K fail | |
| return d; | |
| } | |
| function d3_time_scaleUTCGetYear(d) { | |
| var y = d.getUTCFullYear(), | |
| d0 = d3_time_scaleUTCSetYear(y), | |
| d1 = d3_time_scaleUTCSetYear(y + 1); | |
| return y + (d - d0) / (d1 - d0); | |
| } | |
| d3_time_scaleUTCMethods.year = function(extent, m) { | |
| return d3_time_scaleLinear.domain(extent.map(d3_time_scaleUTCGetYear)).ticks(m).map(d3_time_scaleUTCSetYear); | |
| }; | |
| d3.time.scale.utc = function() { | |
| return d3_time_scale(d3.scale.linear(), d3_time_scaleUTCMethods, d3_time_scaleUTCFormat); | |
| }; | |
| })(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <meta charset="utf-8"> | |
| <style> | |
| circle { | |
| stroke-width: 1.5px; | |
| } | |
| line { | |
| stroke: #999; | |
| stroke-width: 1.5px; | |
| } | |
| .neaktivan{ | |
| stroke: rgba(10,10,10, 0.2) !important; | |
| fill:rgba(10,10,10, 0.2) !important; | |
| } | |
| .labela{ | |
| position: fixed; | |
| top: 50px; | |
| left:30px; | |
| background-color: red; | |
| min-height: 50px; | |
| width: 200px; | |
| } | |
| .legenda{ | |
| position: fixed; | |
| top: 150px; | |
| left:30px; | |
| min-height: 50px; | |
| width: 200px; | |
| } | |
| .izdvojeni{ | |
| stroke:red; | |
| } | |
| circle.izdvojeni:not(.gradjani){ | |
| r:8; | |
| stroke:red; | |
| } | |
| </style> | |
| <body> | |
| <div> | |
| <div class="labela"></div> | |
| <div class="legenda"></div> | |
| </div> | |
| <script src="//d3js.org/d3.v3.min.js"></script> | |
| <script> | |
| /* | |
| grupisanje lokala | |
| zoom out | |
| collapsable | |
| oznacavanje veza | |
| pretraga institucija | |
| grupe institucija | |
| */ | |
| var links = []; | |
| var nodes = []; | |
| var width = 1260, | |
| height = 1000, | |
| radius = 8; | |
| var json1 = {}; | |
| var fill = d3.scale.category20(); | |
| var margin = {left:5, top:100} | |
| //https://bl.ocks.org/mbostock/6123708 | |
| /*var zoom = d3.behavior.zoom() | |
| .scaleExtent([1, 10]) | |
| .on("zoom", zoomed);*/ | |
| var force = d3.layout.force() | |
| .charge(-50) | |
| .linkDistance(20) | |
| .size([width, height]); | |
| var svg = d3.select("body").append("svg") | |
| .attr("width", width) | |
| .attr("height", height) | |
| .append("g") | |
| .attr("transform", "translate(" + margin.left + "," + margin.top + ")scale(0.9)"); | |
| //.call(zoom); | |
| function zoomed() { | |
| svg.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")"); | |
| } | |
| d3.json("veze.json", function(error, json) { | |
| if (error) throw error; | |
| var t = [] | |
| var lokal = []; | |
| var lokal_temp = {"name":'lokal', id:99000, parent_id:0, grupa:"lokal"} | |
| t.push( lokal_temp ); | |
| json.forEach(function( el ){ | |
| if(el.parent_id =="m") | |
| el.parent_id = 0; | |
| var temp = {}; | |
| temp = {"name":el.naziv, id:el.id, parent_id:el.parent_id, grupa:el.grupisanje} | |
| if(el.grupisanje == "lokal" && +el.id == 0){ | |
| temp.parent_id = 99000; | |
| //lokal.push(temp) | |
| }else{ | |
| //t.push(temp); | |
| } | |
| t.push(temp) | |
| }); | |
| //t.filter(function(el) {return el.grupa =="lokal"}); | |
| json1.nodes = t; | |
| nodes = t; | |
| var j = json1.nodes; | |
| //json; | |
| for (var i = j.length - 1; i >= 0; i--) { | |
| var temp_s = indeXof(j[i].id, j); | |
| var temp_t = indeXof(j[i].parent_id, j); | |
| if(temp_s==undefined || temp_t ==undefined) | |
| continue; | |
| var temp_el = {"source": temp_t,"target":temp_s/*, parent_id:j[i].parent_id*/} | |
| links.push(temp_el); | |
| //nodes.push(temp_node); | |
| } | |
| json1.links = links | |
| var boje = { | |
| "posebni":"red", | |
| "nezavisni":"yellow", | |
| "najvisi":"blue", | |
| "lokal":"green", | |
| "ministarstvo":"rgb(0, 255, 205)", | |
| "sud":"rgb(165, 42, 42)", | |
| "tuzilastvo":"rgba(165, 91, 42, 0.8)" | |
| } | |
| var nazivi = Object.keys(boje); | |
| for (var i = nazivi.length - 1; i >= 0; i--) { | |
| var bojaT = boje[ nazivi[i] ]; | |
| //appendChild(document.createElement('p')) | |
| } | |
| legenda(nazivi, boje) | |
| var link = svg.selectAll("line") | |
| .data(json1.links) | |
| .enter().append("line"); | |
| var node = svg.selectAll("circle") | |
| .data(json1.nodes) | |
| .enter().append("circle") //rect | |
| //.attrs({ x: 10, y: 10, width: 80, height: 80, fill: 'red' }) | |
| /*.attr("width","50") | |
| .attr("height","50") | |
| .attr("transform","rotate(45)")*/ | |
| .attr("r",function(d){ | |
| if(d.name == "gradjani") | |
| return 20; | |
| return radius - .75; } | |
| ) | |
| .attr("class", function(d) {return d.grupa;}) | |
| //.attr("labela", function(d){return d.name}) | |
| .style("fill", function(d) { | |
| return boje[d.grupa] ; | |
| }) | |
| .style("stroke", function(d) { return d3.rgb(fill(d.group)).darker(); }) | |
| .on("mouseover",function(d){document.getElementsByClassName('labela')[0].innerHTML = (d.name)}) | |
| .on("click",function(d){ | |
| getNeighbourhood(d); | |
| }) | |
| .call(force.drag); | |
| /*var drag = d3.behavior.drag() | |
| .origin(function(d) { return d; }) | |
| .on("dragstart", dragstarted) | |
| .on("drag", dragged) | |
| .on("dragend", dragended);*/ | |
| //https://bl.ocks.org/puzzler10/4438752bb93f45dc5ad5214efaa12e4a | |
| //http://bl.ocks.org/eyaler/10586116 | |
| /* node.on("dblclick.zoom", function(d) { d3.event.stopPropagation(); | |
| var dcx = (window.innerWidth/2-d.x*zoom.scale()); | |
| var dcy = (window.innerHeight/2-d.y*zoom.scale()); | |
| zoom.translate([dcx,dcy]); | |
| g.attr("transform", "translate("+ dcx + "," + dcy + ")scale(" + zoom.scale() + ")"); | |
| });*/ | |
| force | |
| .nodes(json1.nodes) | |
| .links(json1.links) | |
| .on("tick", tick) | |
| .start(); | |
| function tick(e) { | |
| var k = 0.95 * e.alpha; | |
| // Push sources up and targets down to form a weak tree. | |
| link | |
| .each(function(d) { d.source.y -= k, d.target.y += k; }) | |
| .attr("x1", function(d) { return d.source.x; }) | |
| .attr("y1", function(d) { return d.source.y; }) | |
| .attr("x2", function(d) { return d.target.x; }) | |
| .attr("y2", function(d) { return d.target.y; }); | |
| //x i y za pravugaonike | |
| node | |
| .attr("cx", function(d) { return d.x; }) | |
| .attr("cy", function(d) { return d.y; }); | |
| } | |
| function legenda( elements, boje ) { | |
| elements.push('reset'); | |
| boje.reset = "rgba(30,50,20, 0.8)"; | |
| d3.select(".legenda").selectAll('p') | |
| .data( elements ).enter() | |
| .append('p') | |
| .style("background-color",function(d){ | |
| return boje[d]; | |
| }) | |
| .text(function(d) {return d;}) | |
| .on("click", function(d){ | |
| filterGrupa(d); | |
| /*console.log(d); | |
| alert(d) */ | |
| }) ; | |
| } | |
| function getNeighbourhood(edge){ | |
| //console.log(edge) | |
| resetSelection(); | |
| d3.selectAll("line").classed("izdvojeni",false); | |
| var temp_nodes = []; | |
| d3.selectAll("line") | |
| .filter(function(d) { | |
| //insert oposite one | |
| if(d.source.index === edge.index ) | |
| temp_nodes.push(d.target); | |
| else if(d.target.index === edge.index) | |
| temp_nodes.push(d.source); | |
| if(temp_nodes.indexOf(edge) < 0 ){ | |
| temp_nodes.push(edge); | |
| } | |
| return d.source.index === edge.index | |
| || d.target.index === edge.index; | |
| }) | |
| .classed("izdvojeni",true); | |
| //mozda pomeriti nodove i ponovo iscrtati linije medju njima | |
| d3.selectAll('circle') | |
| .filter(function(d) { return temp_nodes.indexOf(d) >= 0; }) | |
| .classed("izdvojeni",true); | |
| d3.selectAll('circle') | |
| .filter(function(d) { return temp_nodes.indexOf(d) < 0; }) | |
| .classed("neaktivan",true); | |
| // getSiblingLinks(d.source, d.target); | |
| // get nodes | |
| //console.log(la); | |
| } | |
| function resetSelection() { | |
| d3.select("svg").selectAll('circle') | |
| .classed("neaktivan",false) | |
| .classed("izdvojeni",false); | |
| d3.select("svg").selectAll('line') | |
| .classed("izdvojeni",false) | |
| } | |
| function filterGrupa(grupa){ | |
| resetSelection(); | |
| if(grupa !== "reset"){ | |
| d3.select("svg").selectAll('circle:not(.'+grupa+')') | |
| //.data(nazivi).enter() | |
| .classed("neaktivan",true); | |
| //.attr('class', 'neaktivan'); | |
| } | |
| } | |
| function indeXof(el, arr){ | |
| for (var i = arr.length - 1; i >= 0; i--) { | |
| if(arr[i].id == el){ | |
| return i; | |
| } | |
| } | |
| } | |
| }); | |
| </script> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #! /bin/bash | |
| # | |
| # copy latest d3.js from local D3 work repo | |
| # | |
| rm -f d3.v2.js readme.js | |
| pushd ../html/js/d3/ | |
| make | |
| popd | |
| cp ../html/js/d3/d3.v2.js ./readme.js |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| [{"id":"0","naziv":"gradjani","parent_id":"","grupisanje":"gradjani"},{"id":"3911","naziv":"Predsednik opštine Čukarica","parent_id":"3913","grupisanje":"lokal"},{"id":"3912","naziv":"Opštinsko veće Čukarica","parent_id":"3913","grupisanje":"lokal"},{"id":"3913","naziv":"Skupština opštine Čukarica","parent_id":"15001","grupisanje":"lokal"},{"id":"3914","naziv":"Opštinska uprava Čukarica","parent_id":"3912","grupisanje":"lokal"},{"id":"3931","naziv":"Predsednik opštine Novi Beograd","parent_id":"3933","grupisanje":"lokal"},{"id":"3932","naziv":"Opštinsko veće Novi Beograd","parent_id":"3933","grupisanje":"lokal"},{"id":"3933","naziv":"Skupština opštine Novi Beograd","parent_id":"15001","grupisanje":"lokal"},{"id":"3934","naziv":"Opštinska uprava Novi Beograd","parent_id":"3932","grupisanje":"lokal"},{"id":"3959","naziv":"Predsednik opštine Palilula","parent_id":"3961","grupisanje":"lokal"},{"id":"3960","naziv":"Opštinsko veće Palilula","parent_id":"3961","grupisanje":"lokal"},{"id":"3961","naziv":"Skupština opštine Palilula","parent_id":"15001","grupisanje":"lokal"},{"id":"3962","naziv":"Opštinska uprava Palilula","parent_id":"3960","grupisanje":"lokal"},{"id":"3993","naziv":"Predsednik opštine Rakovica","parent_id":"3995","grupisanje":"lokal"},{"id":"3994","naziv":"Opštinsko veće Rakovica","parent_id":"3995","grupisanje":"lokal"},{"id":"3995","naziv":"Skupština opštine Rakovica","parent_id":"15001","grupisanje":"lokal"},{"id":"3996","naziv":"Opštinska uprava Rakovica","parent_id":"3994","grupisanje":"lokal"},{"id":"4021","naziv":"Predsednik opštine Savski venac","parent_id":"4023","grupisanje":"lokal"},{"id":"4022","naziv":"Opštinsko veće Savski venac","parent_id":"4023","grupisanje":"lokal"},{"id":"4023","naziv":"Skupština opštine Savski venac","parent_id":"15001","grupisanje":"lokal"},{"id":"4024","naziv":"Opštinska uprava Savski venac","parent_id":"4022","grupisanje":"lokal"},{"id":"4041","naziv":"Predsednik opštine Stari grad","parent_id":"4043","grupisanje":"lokal"},{"id":"4042","naziv":"Opštinsko veće Stari grad","parent_id":"4043","grupisanje":"lokal"},{"id":"4043","naziv":"Skupština opštine Stari grad","parent_id":"15001","grupisanje":"lokal"},{"id":"4044","naziv":"Opštinska uprava Stari grad","parent_id":"4042","grupisanje":"lokal"},{"id":"4066","naziv":"Predsednik opštine Voždovac","parent_id":"4068","grupisanje":"lokal"},{"id":"4067","naziv":"Opštinsko veće Voždovac","parent_id":"4068","grupisanje":"lokal"},{"id":"4068","naziv":"Skupština opštine Voždovac","parent_id":"15001","grupisanje":"lokal"},{"id":"4069","naziv":"Opštinska uprava Voždovac","parent_id":"4067","grupisanje":"lokal"},{"id":"4102","naziv":"Predsednik opštine Vračar","parent_id":"4104","grupisanje":"lokal"},{"id":"4103","naziv":"Opštinsko veće Vračar","parent_id":"4104","grupisanje":"lokal"},{"id":"4104","naziv":"Skupština opštine Vračar","parent_id":"15001","grupisanje":"lokal"},{"id":"4105","naziv":"Opštinska uprava Vračar","parent_id":"4103","grupisanje":"lokal"},{"id":"4116","naziv":"Predsednik opštine Zemun","parent_id":"4118","grupisanje":"lokal"},{"id":"4117","naziv":"Opštinsko veće Zemun","parent_id":"4118","grupisanje":"lokal"},{"id":"4118","naziv":"Skupština opštine Zemun","parent_id":"15001","grupisanje":"lokal"},{"id":"4119","naziv":"Opštinska uprava Zemun","parent_id":"4117","grupisanje":"lokal"},{"id":"4129","naziv":"Predsednik opštine Zvezdara","parent_id":"4131","grupisanje":"lokal"},{"id":"4130","naziv":"Opštinsko veće Zvezdara","parent_id":"4131","grupisanje":"lokal"},{"id":"4131","naziv":"Skupština opštine Zvezdara","parent_id":"15001","grupisanje":"lokal"},{"id":"4132","naziv":"Opštinska uprava Zvezdara","parent_id":"4130","grupisanje":"lokal"},{"id":"4155","naziv":"Predsednik opštine Barajevo","parent_id":"4157","grupisanje":"lokal"},{"id":"4156","naziv":"Opštinsko veće Barajevo","parent_id":"4157","grupisanje":"lokal"},{"id":"4157","naziv":"Skupština opštine Barajevo","parent_id":"15001","grupisanje":"lokal"},{"id":"4158","naziv":"Opštinska uprava Barajevo","parent_id":"4156","grupisanje":"lokal"},{"id":"4180","naziv":"Predsednik opštine Grocka","parent_id":"4182","grupisanje":"lokal"},{"id":"4181","naziv":"Opštinsko veće Grocka","parent_id":"4182","grupisanje":"lokal"},{"id":"4182","naziv":"Skupština opštine Grocka","parent_id":"15001","grupisanje":"lokal"},{"id":"4183","naziv":"Opštinska uprava Grocka","parent_id":"4181","grupisanje":"lokal"},{"id":"4206","naziv":"Predsednik opštine Lazarevac","parent_id":"4208","grupisanje":"lokal"},{"id":"4207","naziv":"Opštinsko veće Lazarevac","parent_id":"4208","grupisanje":"lokal"},{"id":"4208","naziv":"Skupština opštine Lazarevac","parent_id":"15001","grupisanje":"lokal"},{"id":"4209","naziv":"Opštinska uprava Lazarevac","parent_id":"4207","grupisanje":"lokal"},{"id":"4262","naziv":"Predsednik opštine Mladenovac","parent_id":"4264","grupisanje":"lokal"},{"id":"4263","naziv":"Opštinsko veće Mladenovac","parent_id":"4264","grupisanje":"lokal"},{"id":"4264","naziv":"Skupština opštine Mladenovac","parent_id":"15001","grupisanje":"lokal"},{"id":"4265","naziv":"Opštinska uprava Mladenovac","parent_id":"4263","grupisanje":"lokal"},{"id":"4299","naziv":"Predsednik opštine Obrenovac","parent_id":"4301","grupisanje":"lokal"},{"id":"4300","naziv":"Opštinsko veće Obrenovac","parent_id":"4301","grupisanje":"lokal"},{"id":"4301","naziv":"Skupština opštine Obrenovac","parent_id":"15001","grupisanje":"lokal"},{"id":"4302","naziv":"Opštinska uprava Obrenovac","parent_id":"4300","grupisanje":"lokal"},{"id":"4342","naziv":"Predsednik opštine Sopot","parent_id":"4344","grupisanje":"lokal"},{"id":"4343","naziv":"Opštinsko veće Sopot","parent_id":"4344","grupisanje":"lokal"},{"id":"4344","naziv":"Skupština opštine Sopot","parent_id":"15001","grupisanje":"lokal"},{"id":"4345","naziv":"Opštinska uprava Sopot","parent_id":"4343","grupisanje":"lokal"},{"id":"4366","naziv":"Predsednik opštine Surčin","parent_id":"4368","grupisanje":"lokal"},{"id":"4367","naziv":"Opštinsko veće Surčin","parent_id":"4368","grupisanje":"lokal"},{"id":"4368","naziv":"Skupština opštine Surčin","parent_id":"15001","grupisanje":"lokal"},{"id":"4369","naziv":"Opštinska uprava Surčin","parent_id":"4367","grupisanje":"lokal"},{"id":"4592","naziv":"Predsednik opštine Crveni Krst","parent_id":"4594","grupisanje":"lokal"},{"id":"4593","naziv":"Opštinsko veće Crveni Krst","parent_id":"4594","grupisanje":"lokal"},{"id":"4594","naziv":"Skupština opštine Crveni Krst","parent_id":"15001","grupisanje":"lokal"},{"id":"4595","naziv":"Opštinska uprava Crveni Krst","parent_id":"4593","grupisanje":"lokal"},{"id":"4599","naziv":"Predsednik opštine Medijana","parent_id":"4601","grupisanje":"lokal"},{"id":"4600","naziv":"Opštinsko veće Medijana","parent_id":"4601","grupisanje":"lokal"},{"id":"4601","naziv":"Skupština opštine Medijana","parent_id":"15001","grupisanje":"lokal"},{"id":"4602","naziv":"Opštinska uprava Medijana","parent_id":"4600","grupisanje":"lokal"},{"id":"4612","naziv":"Predsednik opštine Pantelej","parent_id":"4614","grupisanje":"lokal"},{"id":"4613","naziv":"Opštinsko veće Pantelej","parent_id":"4614","grupisanje":"lokal"},{"id":"4614","naziv":"Skupština opštine Pantelej","parent_id":"15001","grupisanje":"lokal"},{"id":"4615","naziv":"Opštinska uprava Pantelej","parent_id":"4613","grupisanje":"lokal"},{"id":"4634","naziv":"Predsednik opštine Palilula","parent_id":"3961","grupisanje":"lokal"},{"id":"4635","naziv":"Opštinsko veće Palilula","parent_id":"3961","grupisanje":"lokal"},{"id":"4636","naziv":"Skupština opštine Palilula","parent_id":"15001","grupisanje":"lokal"},{"id":"4637","naziv":"Opštinska uprava Palilula","parent_id":"3960","grupisanje":"lokal"},{"id":"4653","naziv":"Predsednik opštine Niška Banja","parent_id":"4655","grupisanje":"lokal"},{"id":"4654","naziv":"Opštinsko veće Niška Banja","parent_id":"4655","grupisanje":"lokal"},{"id":"4655","naziv":"Skupština opštine Niška Banja","parent_id":"15001","grupisanje":"lokal"},{"id":"4656","naziv":"Opštinska uprava Niška Banja","parent_id":"4654","grupisanje":"lokal"},{"id":"4718","naziv":"Predsednik opštine Novi Sad","parent_id":"4720","grupisanje":"lokal"},{"id":"4719","naziv":"Opštinsko veće Novi Sad","parent_id":"4720","grupisanje":"lokal"},{"id":"4720","naziv":"Skupština opštine Novi Sad","parent_id":"15001","grupisanje":"lokal"},{"id":"4721","naziv":"Opštinska uprava Novi Sad","parent_id":"4719","grupisanje":"lokal"},{"id":"6569","naziv":"Predsednik opštine Bačka Topola","parent_id":"6571","grupisanje":"lokal"},{"id":"6570","naziv":"Opštinsko veće Bačka Topola","parent_id":"6571","grupisanje":"lokal"},{"id":"6571","naziv":"Skupština opštine Bačka Topola","parent_id":"15001","grupisanje":"lokal"},{"id":"6572","naziv":"Opštinska uprava Bačka Topola","parent_id":"6570","grupisanje":"lokal"},{"id":"6600","naziv":"Predsednik opštine Mali Iđoš","parent_id":"6602","grupisanje":"lokal"},{"id":"6601","naziv":"Opštinsko veće Mali Iđoš","parent_id":"6602","grupisanje":"lokal"},{"id":"6602","naziv":"Skupština opštine Mali Iđoš","parent_id":"15001","grupisanje":"lokal"},{"id":"6603","naziv":"Opštinska uprava Mali Iđoš","parent_id":"6601","grupisanje":"lokal"},{"id":"6615","naziv":"Predsednik opštine Žitište","parent_id":"6617","grupisanje":"lokal"},{"id":"6616","naziv":"Opštinsko veće Žitište","parent_id":"6617","grupisanje":"lokal"},{"id":"6617","naziv":"Skupština opštine Žitište","parent_id":"15001","grupisanje":"lokal"},{"id":"6618","naziv":"Opštinska uprava Žitište","parent_id":"6616","grupisanje":"lokal"},{"id":"6639","naziv":"Predsednik opštine Nova Crnja","parent_id":"6641","grupisanje":"lokal"},{"id":"6640","naziv":"Opštinsko veće Nova Crnja","parent_id":"6641","grupisanje":"lokal"},{"id":"6641","naziv":"Skupština opštine Nova Crnja","parent_id":"15001","grupisanje":"lokal"},{"id":"6642","naziv":"Opštinska uprava Nova Crnja","parent_id":"6640","grupisanje":"lokal"},{"id":"6653","naziv":"Predsednik opštine Novi Bečej","parent_id":"6655","grupisanje":"lokal"},{"id":"6654","naziv":"Opštinsko veće Novi Bečej","parent_id":"6655","grupisanje":"lokal"},{"id":"6655","naziv":"Skupština opštine Novi Bečej","parent_id":"15001","grupisanje":"lokal"},{"id":"6656","naziv":"Opštinska uprava Novi Bečej","parent_id":"6654","grupisanje":"lokal"},{"id":"6672","naziv":"Predsednik opštine Sečanj","parent_id":"6674","grupisanje":"lokal"},{"id":"6673","naziv":"Opštinsko veće Sečanj","parent_id":"6674","grupisanje":"lokal"},{"id":"6674","naziv":"Skupština opštine Sečanj","parent_id":"15001","grupisanje":"lokal"},{"id":"6675","naziv":"Opštinska uprava Sečanj","parent_id":"6673","grupisanje":"lokal"},{"id":"6696","naziv":"Predsednik opštine Ada","parent_id":"6698","grupisanje":"lokal"},{"id":"6697","naziv":"Opštinsko veće Ada","parent_id":"6698","grupisanje":"lokal"},{"id":"6698","naziv":"Skupština opštine Ada","parent_id":"15001","grupisanje":"lokal"},{"id":"6699","naziv":"Opštinska uprava Ada","parent_id":"6697","grupisanje":"lokal"},{"id":"6711","naziv":"Predsednik opštine Kanjiža","parent_id":"6713","grupisanje":"lokal"},{"id":"6712","naziv":"Opštinsko veće Kanjiža","parent_id":"6713","grupisanje":"lokal"},{"id":"6713","naziv":"Skupština opštine Kanjiža","parent_id":"15001","grupisanje":"lokal"},{"id":"6714","naziv":"Opštinska uprava Kanjiža","parent_id":"6712","grupisanje":"lokal"},{"id":"6735","naziv":"Predsednik opštine Novi Kneževac","parent_id":"6737","grupisanje":"lokal"},{"id":"6736","naziv":"Opštinsko veće Novi Kneževac","parent_id":"6737","grupisanje":"lokal"},{"id":"6737","naziv":"Skupština opštine Novi Kneževac","parent_id":"15001","grupisanje":"lokal"},{"id":"6738","naziv":"Opštinska uprava Novi Kneževac","parent_id":"6736","grupisanje":"lokal"},{"id":"6750","naziv":"Predsednik opštine Senta","parent_id":"6752","grupisanje":"lokal"},{"id":"6751","naziv":"Opštinsko veće Senta","parent_id":"6752","grupisanje":"lokal"},{"id":"6752","naziv":"Skupština opštine Senta","parent_id":"15001","grupisanje":"lokal"},{"id":"6753","naziv":"Opštinska uprava Senta","parent_id":"6751","grupisanje":"lokal"},{"id":"6776","naziv":"Predsednik opštine Čoka","parent_id":"6778","grupisanje":"lokal"},{"id":"6777","naziv":"Opštinsko veće Čoka","parent_id":"6778","grupisanje":"lokal"},{"id":"6778","naziv":"Skupština opštine Čoka","parent_id":"15001","grupisanje":"lokal"},{"id":"6779","naziv":"Opštinska uprava Čoka","parent_id":"6777","grupisanje":"lokal"},{"id":"6795","naziv":"Predsednik opštine Alibunar","parent_id":"6797","grupisanje":"lokal"},{"id":"6796","naziv":"Opštinsko veće Alibunar","parent_id":"6797","grupisanje":"lokal"},{"id":"6797","naziv":"Skupština opštine Alibunar","parent_id":"15001","grupisanje":"lokal"},{"id":"6798","naziv":"Opštinska uprava Alibunar","parent_id":"6796","grupisanje":"lokal"},{"id":"6827","naziv":"Predsednik opštine Bela Crkva","parent_id":"6829","grupisanje":"lokal"},{"id":"6828","naziv":"Opštinsko veće Bela Crkva","parent_id":"6829","grupisanje":"lokal"},{"id":"6829","naziv":"Skupština opštine Bela Crkva","parent_id":"15001","grupisanje":"lokal"},{"id":"6830","naziv":"Opštinska uprava Bela Crkva","parent_id":"6828","grupisanje":"lokal"},{"id":"6859","naziv":"Predsednik opštine Kovačica","parent_id":"6861","grupisanje":"lokal"},{"id":"6860","naziv":"Opštinsko veće Kovačica","parent_id":"6861","grupisanje":"lokal"},{"id":"6861","naziv":"Skupština opštine Kovačica","parent_id":"15001","grupisanje":"lokal"},{"id":"6862","naziv":"Opštinska uprava Kovačica","parent_id":"6860","grupisanje":"lokal"},{"id":"6893","naziv":"Predsednik opštine Kovin","parent_id":"6895","grupisanje":"lokal"},{"id":"6894","naziv":"Opštinsko veće Kovin","parent_id":"6895","grupisanje":"lokal"},{"id":"6895","naziv":"Skupština opštine Kovin","parent_id":"15001","grupisanje":"lokal"},{"id":"6896","naziv":"Opštinska uprava Kovin","parent_id":"6894","grupisanje":"lokal"},{"id":"6919","naziv":"Predsednik opštine Opovo","parent_id":"6921","grupisanje":"lokal"},{"id":"6920","naziv":"Opštinsko veće Opovo","parent_id":"6921","grupisanje":"lokal"},{"id":"6921","naziv":"Skupština opštine Opovo","parent_id":"15001","grupisanje":"lokal"},{"id":"6922","naziv":"Opštinska uprava Opovo","parent_id":"6920","grupisanje":"lokal"},{"id":"6933","naziv":"Predsednik opštine Plandište","parent_id":"6935","grupisanje":"lokal"},{"id":"6934","naziv":"Opštinsko veće Plandište","parent_id":"6935","grupisanje":"lokal"},{"id":"6935","naziv":"Skupština opštine Plandište","parent_id":"15001","grupisanje":"lokal"},{"id":"6936","naziv":"Opštinska uprava Plandište","parent_id":"6934","grupisanje":"lokal"},{"id":"6959","naziv":"Predsednik opštine Apatin","parent_id":"6961","grupisanje":"lokal"},{"id":"6960","naziv":"Opštinsko veće Apatin","parent_id":"6961","grupisanje":"lokal"},{"id":"6961","naziv":"Skupština opštine Apatin","parent_id":"15001","grupisanje":"lokal"},{"id":"6962","naziv":"Opštinska uprava Apatin","parent_id":"6960","grupisanje":"lokal"},{"id":"6980","naziv":"Predsednik opštine Kula","parent_id":"6982","grupisanje":"lokal"},{"id":"6981","naziv":"Opštinsko veće Kula","parent_id":"6982","grupisanje":"lokal"},{"id":"6982","naziv":"Skupština opštine Kula","parent_id":"15001","grupisanje":"lokal"},{"id":"6983","naziv":"Opštinska uprava Kula","parent_id":"6981","grupisanje":"lokal"},{"id":"7009","naziv":"Predsednik opštine Odžaci","parent_id":"7011","grupisanje":"lokal"},{"id":"7010","naziv":"Opštinsko veće Odžaci","parent_id":"7011","grupisanje":"lokal"},{"id":"7011","naziv":"Skupština opštine Odžaci","parent_id":"15001","grupisanje":"lokal"},{"id":"7012","naziv":"Opštinska uprava Odžaci","parent_id":"7010","grupisanje":"lokal"},{"id":"7031","naziv":"Predsednik opštine Bač","parent_id":"7033","grupisanje":"lokal"},{"id":"7032","naziv":"Opštinsko veće Bač","parent_id":"7033","grupisanje":"lokal"},{"id":"7033","naziv":"Skupština opštine Bač","parent_id":"15001","grupisanje":"lokal"},{"id":"7034","naziv":"Opštinska uprava Bač","parent_id":"7032","grupisanje":"lokal"},{"id":"7054","naziv":"Predsednik opštine Bačka Palanka","parent_id":"7056","grupisanje":"lokal"},{"id":"7055","naziv":"Opštinsko veće Bačka Palanka","parent_id":"7056","grupisanje":"lokal"},{"id":"7056","naziv":"Skupština opštine Bačka Palanka","parent_id":"15001","grupisanje":"lokal"},{"id":"7057","naziv":"Opštinska uprava Bačka Palanka","parent_id":"7055","grupisanje":"lokal"},{"id":"7091","naziv":"Predsednik opštine Bački Petrovac","parent_id":"7093","grupisanje":"lokal"},{"id":"7092","naziv":"Opštinsko veće Bački Petrovac","parent_id":"7093","grupisanje":"lokal"},{"id":"7093","naziv":"Skupština opštine Bački Petrovac","parent_id":"15001","grupisanje":"lokal"},{"id":"7094","naziv":"Opštinska uprava Bački Petrovac","parent_id":"7092","grupisanje":"lokal"},{"id":"7116","naziv":"Predsednik opštine Beočin","parent_id":"7118","grupisanje":"lokal"},{"id":"7117","naziv":"Opštinsko veće Beočin","parent_id":"7118","grupisanje":"lokal"},{"id":"7118","naziv":"Skupština opštine Beočin","parent_id":"15001","grupisanje":"lokal"},{"id":"7119","naziv":"Opštinska uprava Beočin","parent_id":"7117","grupisanje":"lokal"},{"id":"7141","naziv":"Predsednik opštine Bečej","parent_id":"7143","grupisanje":"lokal"},{"id":"7142","naziv":"Opštinsko veće Bečej","parent_id":"7143","grupisanje":"lokal"},{"id":"7143","naziv":"Skupština opštine Bečej","parent_id":"15001","grupisanje":"lokal"},{"id":"7144","naziv":"Opštinska uprava Bečej","parent_id":"7142","grupisanje":"lokal"},{"id":"7168","naziv":"Predsednik opštine Vrbas","parent_id":"7170","grupisanje":"lokal"},{"id":"7169","naziv":"Opštinsko veće Vrbas","parent_id":"7170","grupisanje":"lokal"},{"id":"7170","naziv":"Skupština opštine Vrbas","parent_id":"15001","grupisanje":"lokal"},{"id":"7171","naziv":"Opštinska uprava Vrbas","parent_id":"7169","grupisanje":"lokal"},{"id":"7194","naziv":"Predsednik opštine Žabalj","parent_id":"7196","grupisanje":"lokal"},{"id":"7195","naziv":"Opštinsko veće Žabalj","parent_id":"7196","grupisanje":"lokal"},{"id":"7196","naziv":"Skupština opštine Žabalj","parent_id":"15001","grupisanje":"lokal"},{"id":"7197","naziv":"Opštinska uprava Žabalj","parent_id":"7195","grupisanje":"lokal"},{"id":"7215","naziv":"Predsednik opštine Srbobran","parent_id":"7217","grupisanje":"lokal"},{"id":"7216","naziv":"Opštinsko veće Srbobran","parent_id":"7217","grupisanje":"lokal"},{"id":"7217","naziv":"Skupština opštine Srbobran","parent_id":"15001","grupisanje":"lokal"},{"id":"7218","naziv":"Opštinska uprava Srbobran","parent_id":"7216","grupisanje":"lokal"},{"id":"7229","naziv":"Predsednik opštine Sremski Karlovci","parent_id":"7231","grupisanje":"lokal"},{"id":"7230","naziv":"Opštinsko veće Sremski Karlovci","parent_id":"7231","grupisanje":"lokal"},{"id":"7231","naziv":"Skupština opštine Sremski Karlovci","parent_id":"15001","grupisanje":"lokal"},{"id":"7232","naziv":"Opštinska uprava Sremski Karlovci","parent_id":"7230","grupisanje":"lokal"},{"id":"7237","naziv":"Predsednik opštine Temerin","parent_id":"7239","grupisanje":"lokal"},{"id":"7238","naziv":"Opštinsko veće Temerin","parent_id":"7239","grupisanje":"lokal"},{"id":"7239","naziv":"Skupština opštine Temerin","parent_id":"15001","grupisanje":"lokal"},{"id":"7240","naziv":"Opštinska uprava Temerin","parent_id":"7238","grupisanje":"lokal"},{"id":"7257","naziv":"Predsednik opštine Titel","parent_id":"7259","grupisanje":"lokal"},{"id":"7258","naziv":"Opštinsko veće Titel","parent_id":"7259","grupisanje":"lokal"},{"id":"7259","naziv":"Skupština opštine Titel","parent_id":"15001","grupisanje":"lokal"},{"id":"7260","naziv":"Opštinska uprava Titel","parent_id":"7258","grupisanje":"lokal"},{"id":"7276","naziv":"Predsednik opštine Inđija","parent_id":"7278","grupisanje":"lokal"},{"id":"7277","naziv":"Opštinsko veće Inđija","parent_id":"7278","grupisanje":"lokal"},{"id":"7278","naziv":"Skupština opštine Inđija","parent_id":"15001","grupisanje":"lokal"},{"id":"7279","naziv":"Opštinska uprava Inđija","parent_id":"7277","grupisanje":"lokal"},{"id":"7309","naziv":"Predsednik opštine Irig","parent_id":"7311","grupisanje":"lokal"},{"id":"7310","naziv":"Opštinsko veće Irig","parent_id":"7311","grupisanje":"lokal"},{"id":"7311","naziv":"Skupština opštine Irig","parent_id":"15001","grupisanje":"lokal"},{"id":"7312","naziv":"Opštinska uprava Irig","parent_id":"7310","grupisanje":"lokal"},{"id":"7330","naziv":"Predsednik opštine Pećinci","parent_id":"7332","grupisanje":"lokal"},{"id":"7331","naziv":"Opštinsko veće Pećinci","parent_id":"7332","grupisanje":"lokal"},{"id":"7332","naziv":"Skupština opštine Pećinci","parent_id":"15001","grupisanje":"lokal"},{"id":"7333","naziv":"Opštinska uprava Pećinci","parent_id":"7331","grupisanje":"lokal"},{"id":"7360","naziv":"Predsednik opštine Ruma","parent_id":"7362","grupisanje":"lokal"},{"id":"7361","naziv":"Opštinsko veće Ruma","parent_id":"7362","grupisanje":"lokal"},{"id":"7362","naziv":"Skupština opštine Ruma","parent_id":"15001","grupisanje":"lokal"},{"id":"7363","naziv":"Opštinska uprava Ruma","parent_id":"7361","grupisanje":"lokal"},{"id":"7400","naziv":"Predsednik opštine Stara Pazova","parent_id":"7402","grupisanje":"lokal"},{"id":"7401","naziv":"Opštinsko veće Stara Pazova","parent_id":"7402","grupisanje":"lokal"},{"id":"7402","naziv":"Skupština opštine Stara Pazova","parent_id":"15001","grupisanje":"lokal"},{"id":"7430","naziv":"Predsednik opštine Šid","parent_id":"7432","grupisanje":"lokal"},{"id":"7431","naziv":"Opštinsko veće Šid","parent_id":"7432","grupisanje":"lokal"},{"id":"7432","naziv":"Skupština opštine Šid","parent_id":"15001","grupisanje":"lokal"},{"id":"7433","naziv":"Opštinska uprava Šid","parent_id":"7431","grupisanje":"lokal"},{"id":"7467","naziv":"Predsednik opštine Bogatić","parent_id":"7469","grupisanje":"lokal"},{"id":"7468","naziv":"Opštinsko veće Bogatić","parent_id":"7469","grupisanje":"lokal"},{"id":"7469","naziv":"Skupština opštine Bogatić","parent_id":"15001","grupisanje":"lokal"},{"id":"7470","naziv":"Opštinska uprava Bogatić","parent_id":"7468","grupisanje":"lokal"},{"id":"7493","naziv":"Predsednik opštine Vladimirci","parent_id":"7495","grupisanje":"lokal"},{"id":"7494","naziv":"Opštinsko veće Vladimirci","parent_id":"7495","grupisanje":"lokal"},{"id":"7495","naziv":"Skupština opštine Vladimirci","parent_id":"15001","grupisanje":"lokal"},{"id":"7496","naziv":"Opštinska uprava Vladimirci","parent_id":"7494","grupisanje":"lokal"},{"id":"7536","naziv":"Predsednik opštine Koceljeva","parent_id":"7538","grupisanje":"lokal"},{"id":"7537","naziv":"Opštinsko veće Koceljeva","parent_id":"7538","grupisanje":"lokal"},{"id":"7538","naziv":"Skupština opštine Koceljeva","parent_id":"15001","grupisanje":"lokal"},{"id":"7539","naziv":"Opštinska uprava Koceljeva","parent_id":"7537","grupisanje":"lokal"},{"id":"7562","naziv":"Predsednik opštine Krupanj","parent_id":"7564","grupisanje":"lokal"},{"id":"7563","naziv":"Opštinsko veće Krupanj","parent_id":"7564","grupisanje":"lokal"},{"id":"7564","naziv":"Skupština opštine Krupanj","parent_id":"15001","grupisanje":"lokal"},{"id":"7565","naziv":"Opštinska uprava Krupanj","parent_id":"7563","grupisanje":"lokal"},{"id":"7599","naziv":"Predsednik opštine Ljubovija","parent_id":"7601","grupisanje":"lokal"},{"id":"7600","naziv":"Opštinsko veće Ljubovija","parent_id":"7601","grupisanje":"lokal"},{"id":"7601","naziv":"Skupština opštine Ljubovija","parent_id":"15001","grupisanje":"lokal"},{"id":"7602","naziv":"Opštinska uprava Ljubovija","parent_id":"7600","grupisanje":"lokal"},{"id":"7636","naziv":"Predsednik opštine Mali Zvornik","parent_id":"7638","grupisanje":"lokal"},{"id":"7637","naziv":"Opštinsko veće Mali Zvornik","parent_id":"7638","grupisanje":"lokal"},{"id":"7638","naziv":"Skupština opštine Mali Zvornik","parent_id":"15001","grupisanje":"lokal"},{"id":"7639","naziv":"Opštinska uprava Mali Zvornik","parent_id":"7637","grupisanje":"lokal"},{"id":"7660","naziv":"Predsednik opštine Lajkovac","parent_id":"7662","grupisanje":"lokal"},{"id":"7661","naziv":"Opštinsko veće Lajkovac","parent_id":"7662","grupisanje":"lokal"},{"id":"7662","naziv":"Skupština opštine Lajkovac","parent_id":"15001","grupisanje":"lokal"},{"id":"7663","naziv":"Opštinska uprava Lajkovac","parent_id":"7661","grupisanje":"lokal"},{"id":"7693","naziv":"Predsednik opštine Ljig","parent_id":"7695","grupisanje":"lokal"},{"id":"7694","naziv":"Opštinsko veće Ljig","parent_id":"7695","grupisanje":"lokal"},{"id":"7695","naziv":"Skupština opštine Ljig","parent_id":"15001","grupisanje":"lokal"},{"id":"7696","naziv":"Opštinska uprava Ljig","parent_id":"7694","grupisanje":"lokal"},{"id":"7729","naziv":"Predsednik opštine Mionica","parent_id":"7731","grupisanje":"lokal"},{"id":"7730","naziv":"Opštinsko veće Mionica","parent_id":"7731","grupisanje":"lokal"},{"id":"7731","naziv":"Skupština opštine Mionica","parent_id":"15001","grupisanje":"lokal"},{"id":"7732","naziv":"Opštinska uprava Mionica","parent_id":"7730","grupisanje":"lokal"},{"id":"7776","naziv":"Predsednik opštine Osečina","parent_id":"7778","grupisanje":"lokal"},{"id":"7777","naziv":"Opštinsko veće Osečina","parent_id":"7778","grupisanje":"lokal"},{"id":"7778","naziv":"Skupština opštine Osečina","parent_id":"15001","grupisanje":"lokal"},{"id":"7779","naziv":"Opštinska uprava Osečina","parent_id":"7777","grupisanje":"lokal"},{"id":"7810","naziv":"Predsednik opštine Ub","parent_id":"7812","grupisanje":"lokal"},{"id":"7811","naziv":"Opštinsko veće Ub","parent_id":"7812","grupisanje":"lokal"},{"id":"7812","naziv":"Skupština opštine Ub","parent_id":"15001","grupisanje":"lokal"},{"id":"7813","naziv":"Opštinska uprava Ub","parent_id":"7811","grupisanje":"lokal"},{"id":"7858","naziv":"Predsednik opštine Velika Plana","parent_id":"7860","grupisanje":"lokal"},{"id":"7859","naziv":"Opštinsko veće Velika Plana","parent_id":"7860","grupisanje":"lokal"},{"id":"7860","naziv":"Skupština opštine Velika Plana","parent_id":"15001","grupisanje":"lokal"},{"id":"7861","naziv":"Opštinska uprava Velika Plana","parent_id":"7859","grupisanje":"lokal"},{"id":"7894","naziv":"Predsednik opštine Smederevska Palanka","parent_id":"7896","grupisanje":"lokal"},{"id":"7895","naziv":"Opštinsko veće Smederevska Palanka","parent_id":"7896","grupisanje":"lokal"},{"id":"7896","naziv":"Skupština opštine Smederevska Palanka","parent_id":"15001","grupisanje":"lokal"},{"id":"7897","naziv":"Opštinska uprava Smederevska Palanka","parent_id":"7895","grupisanje":"lokal"},{"id":"7935","naziv":"Predsednik opštine Veliko Gradište","parent_id":"7937","grupisanje":"lokal"},{"id":"7936","naziv":"Opštinsko veće Veliko Gradište","parent_id":"7937","grupisanje":"lokal"},{"id":"7937","naziv":"Skupština opštine Veliko Gradište","parent_id":"15001","grupisanje":"lokal"},{"id":"7938","naziv":"Opštinska uprava Veliko Gradište","parent_id":"7936","grupisanje":"lokal"},{"id":"7975","naziv":"Predsednik opštine Golubac","parent_id":"7977","grupisanje":"lokal"},{"id":"7976","naziv":"Opštinsko veće Golubac","parent_id":"7977","grupisanje":"lokal"},{"id":"7977","naziv":"Skupština opštine Golubac","parent_id":"15001","grupisanje":"lokal"},{"id":"7978","naziv":"Opštinska uprava Golubac","parent_id":"7976","grupisanje":"lokal"},{"id":"8011","naziv":"Predsednik opštine Žabari","parent_id":"8013","grupisanje":"lokal"},{"id":"8012","naziv":"Opštinsko veće Žabari","parent_id":"8013","grupisanje":"lokal"},{"id":"8013","naziv":"Skupština opštine Žabari","parent_id":"15001","grupisanje":"lokal"},{"id":"8014","naziv":"Opštinska uprava Žabari","parent_id":"8012","grupisanje":"lokal"},{"id":"8040","naziv":"Predsednik opštine Žagubica","parent_id":"8042","grupisanje":"lokal"},{"id":"8041","naziv":"Opštinsko veće Žagubica","parent_id":"8042","grupisanje":"lokal"},{"id":"8042","naziv":"Skupština opštine Žagubica","parent_id":"15001","grupisanje":"lokal"},{"id":"8043","naziv":"Opštinska uprava Žagubica","parent_id":"8041","grupisanje":"lokal"},{"id":"8067","naziv":"Predsednik opštine Kučevo","parent_id":"8069","grupisanje":"lokal"},{"id":"8068","naziv":"Opštinsko veće Kučevo","parent_id":"8069","grupisanje":"lokal"},{"id":"8069","naziv":"Skupština opštine Kučevo","parent_id":"15001","grupisanje":"lokal"},{"id":"8070","naziv":"Opštinska uprava Kučevo","parent_id":"8068","grupisanje":"lokal"},{"id":"8102","naziv":"Predsednik opštine Malo Crniće","parent_id":"8104","grupisanje":"lokal"},{"id":"8103","naziv":"Opštinsko veće Malo Crniće","parent_id":"8104","grupisanje":"lokal"},{"id":"8104","naziv":"Skupština opštine Malo Crniće","parent_id":"15001","grupisanje":"lokal"},{"id":"8105","naziv":"Opštinska uprava Malo Crniće","parent_id":"8103","grupisanje":"lokal"},{"id":"8133","naziv":"Predsednik opštine Petrovac na Mlavi","parent_id":"8135","grupisanje":"lokal"},{"id":"8134","naziv":"Opštinsko veće Petrovac na Mlavi","parent_id":"8135","grupisanje":"lokal"},{"id":"8135","naziv":"Skupština opštine Petrovac na Mlavi","parent_id":"15001","grupisanje":"lokal"},{"id":"8136","naziv":"Opštinska uprava Petrovac na Mlavi","parent_id":"8134","grupisanje":"lokal"},{"id":"8187","naziv":"Predsednik opštine Aranđelovac","parent_id":"8189","grupisanje":"lokal"},{"id":"8188","naziv":"Opštinsko veće Aranđelovac","parent_id":"8189","grupisanje":"lokal"},{"id":"8189","naziv":"Skupština opštine Aranđelovac","parent_id":"15001","grupisanje":"lokal"},{"id":"8190","naziv":"Opštinska uprava Aranđelovac","parent_id":"8188","grupisanje":"lokal"},{"id":"8229","naziv":"Predsednik opštine Batočina","parent_id":"8231","grupisanje":"lokal"},{"id":"8230","naziv":"Opštinsko veće Batočina","parent_id":"8231","grupisanje":"lokal"},{"id":"8231","naziv":"Skupština opštine Batočina","parent_id":"15001","grupisanje":"lokal"},{"id":"8232","naziv":"Opštinska uprava Batočina","parent_id":"8230","grupisanje":"lokal"},{"id":"8252","naziv":"Predsednik opštine Knić","parent_id":"8254","grupisanje":"lokal"},{"id":"8253","naziv":"Opštinsko veće Knić","parent_id":"8254","grupisanje":"lokal"},{"id":"8254","naziv":"Skupština opštine Knić","parent_id":"15001","grupisanje":"lokal"},{"id":"8255","naziv":"Opštinska uprava Knić","parent_id":"8253","grupisanje":"lokal"},{"id":"8295","naziv":"Predsednik opštine Lapovo","parent_id":"8297","grupisanje":"lokal"},{"id":"8296","naziv":"Opštinsko veće Lapovo","parent_id":"8297","grupisanje":"lokal"},{"id":"8297","naziv":"Skupština opštine Lapovo","parent_id":"15001","grupisanje":"lokal"},{"id":"8298","naziv":"Opštinska uprava Lapovo","parent_id":"8296","grupisanje":"lokal"},{"id":"8306","naziv":"Predsednik opštine Rača","parent_id":"8308","grupisanje":"lokal"},{"id":"8307","naziv":"Opštinsko veće Rača","parent_id":"8308","grupisanje":"lokal"},{"id":"8308","naziv":"Skupština opštine Rača","parent_id":"15001","grupisanje":"lokal"},{"id":"8309","naziv":"Opštinska uprava Rača","parent_id":"8307","grupisanje":"lokal"},{"id":"8336","naziv":"Predsednik opštine Topola","parent_id":"8338","grupisanje":"lokal"},{"id":"8337","naziv":"Opštinsko veće Topola","parent_id":"8338","grupisanje":"lokal"},{"id":"8338","naziv":"Skupština opštine Topola","parent_id":"15001","grupisanje":"lokal"},{"id":"8339","naziv":"Opštinska uprava Topola","parent_id":"8337","grupisanje":"lokal"},{"id":"8378","naziv":"Predsednik opštine Despotovac","parent_id":"8380","grupisanje":"lokal"},{"id":"8379","naziv":"Opštinsko veće Despotovac","parent_id":"8380","grupisanje":"lokal"},{"id":"8380","naziv":"Skupština opštine Despotovac","parent_id":"15001","grupisanje":"lokal"},{"id":"8381","naziv":"Opštinska uprava Despotovac","parent_id":"8379","grupisanje":"lokal"},{"id":"8426","naziv":"Predsednik opštine Paraćin","parent_id":"8428","grupisanje":"lokal"},{"id":"8427","naziv":"Opštinsko veće Paraćin","parent_id":"8428","grupisanje":"lokal"},{"id":"8428","naziv":"Skupština opštine Paraćin","parent_id":"15001","grupisanje":"lokal"},{"id":"8429","naziv":"Opštinska uprava Paraćin","parent_id":"8427","grupisanje":"lokal"},{"id":"8483","naziv":"Predsednik opštine Rekovac","parent_id":"8485","grupisanje":"lokal"},{"id":"8484","naziv":"Opštinsko veće Rekovac","parent_id":"8485","grupisanje":"lokal"},{"id":"8485","naziv":"Skupština opštine Rekovac","parent_id":"15001","grupisanje":"lokal"},{"id":"8486","naziv":"Opštinska uprava Rekovac","parent_id":"8484","grupisanje":"lokal"},{"id":"8530","naziv":"Predsednik opštine Svilajnac","parent_id":"8532","grupisanje":"lokal"},{"id":"8531","naziv":"Opštinsko veće Svilajnac","parent_id":"8532","grupisanje":"lokal"},{"id":"8532","naziv":"Skupština opštine Svilajnac","parent_id":"15001","grupisanje":"lokal"},{"id":"8533","naziv":"Opštinska uprava Svilajnac","parent_id":"8531","grupisanje":"lokal"},{"id":"8566","naziv":"Predsednik opštine Ćuprija","parent_id":"8568","grupisanje":"lokal"},{"id":"8567","naziv":"Opštinsko veće Ćuprija","parent_id":"8568","grupisanje":"lokal"},{"id":"8568","naziv":"Skupština opštine Ćuprija","parent_id":"15001","grupisanje":"lokal"},{"id":"8569","naziv":"Opštinska uprava Ćuprija","parent_id":"8567","grupisanje":"lokal"},{"id":"8651","naziv":"Predsednik opštine Kladovo","parent_id":"8653","grupisanje":"lokal"},{"id":"8652","naziv":"Opštinsko veće Kladovo","parent_id":"8653","grupisanje":"lokal"},{"id":"8653","naziv":"Skupština opštine Kladovo","parent_id":"15001","grupisanje":"lokal"},{"id":"8654","naziv":"Opštinska uprava Kladovo","parent_id":"8652","grupisanje":"lokal"},{"id":"8693","naziv":"Predsednik opštine Majdanpek","parent_id":"8695","grupisanje":"lokal"},{"id":"8694","naziv":"Opštinsko veće Majdanpek","parent_id":"8695","grupisanje":"lokal"},{"id":"8695","naziv":"Skupština opštine Majdanpek","parent_id":"15001","grupisanje":"lokal"},{"id":"8696","naziv":"Opštinska uprava Majdanpek","parent_id":"8694","grupisanje":"lokal"},{"id":"8722","naziv":"Predsednik opštine Negotin","parent_id":"8724","grupisanje":"lokal"},{"id":"8723","naziv":"Opštinsko veće Negotin","parent_id":"8724","grupisanje":"lokal"},{"id":"8724","naziv":"Skupština opštine Negotin","parent_id":"15001","grupisanje":"lokal"},{"id":"8725","naziv":"Opštinska uprava Negotin","parent_id":"8723","grupisanje":"lokal"},{"id":"8784","naziv":"Predsednik opštine Boljevac","parent_id":"8786","grupisanje":"lokal"},{"id":"8785","naziv":"Opštinsko veće Boljevac","parent_id":"8786","grupisanje":"lokal"},{"id":"8786","naziv":"Skupština opštine Boljevac","parent_id":"15001","grupisanje":"lokal"},{"id":"8787","naziv":"Opštinska uprava Boljevac","parent_id":"8785","grupisanje":"lokal"},{"id":"8820","naziv":"Predsednik opštine Knjaževac","parent_id":"8822","grupisanje":"lokal"},{"id":"8821","naziv":"Opštinsko veće Knjaževac","parent_id":"8822","grupisanje":"lokal"},{"id":"8822","naziv":"Skupština opštine Knjaževac","parent_id":"15001","grupisanje":"lokal"},{"id":"8823","naziv":"Opštinska uprava Knjaževac","parent_id":"8821","grupisanje":"lokal"},{"id":"8912","naziv":"Predsednik opštine Sokobanja","parent_id":"8914","grupisanje":"lokal"},{"id":"8913","naziv":"Opštinsko veće Sokobanja","parent_id":"8914","grupisanje":"lokal"},{"id":"8914","naziv":"Skupština opštine Sokobanja","parent_id":"15001","grupisanje":"lokal"},{"id":"8915","naziv":"Opštinska uprava Sokobanja","parent_id":"8913","grupisanje":"lokal"},{"id":"8948","naziv":"Predsednik opštine Arilje","parent_id":"8950","grupisanje":"lokal"},{"id":"8949","naziv":"Opštinsko veće Arilje","parent_id":"8950","grupisanje":"lokal"},{"id":"8950","naziv":"Skupština opštine Arilje","parent_id":"15001","grupisanje":"lokal"},{"id":"8951","naziv":"Opštinska uprava Arilje","parent_id":"8949","grupisanje":"lokal"},{"id":"8982","naziv":"Predsednik opštine Bajina Bašta","parent_id":"8984","grupisanje":"lokal"},{"id":"8983","naziv":"Opštinsko veće Bajina Bašta","parent_id":"8984","grupisanje":"lokal"},{"id":"8984","naziv":"Skupština opštine Bajina Bašta","parent_id":"15001","grupisanje":"lokal"},{"id":"8985","naziv":"Opštinska uprava Bajina Bašta","parent_id":"8983","grupisanje":"lokal"},{"id":"9026","naziv":"Predsednik opštine Kosjerić","parent_id":"9028","grupisanje":"lokal"},{"id":"9027","naziv":"Opštinsko veće Kosjerić","parent_id":"9028","grupisanje":"lokal"},{"id":"9028","naziv":"Skupština opštine Kosjerić","parent_id":"15001","grupisanje":"lokal"},{"id":"9029","naziv":"Opštinska uprava Kosjerić","parent_id":"9027","grupisanje":"lokal"},{"id":"9056","naziv":"Predsednik opštine Nova Varoš","parent_id":"9058","grupisanje":"lokal"},{"id":"9057","naziv":"Opštinsko veće Nova Varoš","parent_id":"9058","grupisanje":"lokal"},{"id":"9058","naziv":"Skupština opštine Nova Varoš","parent_id":"15001","grupisanje":"lokal"},{"id":"9059","naziv":"Opštinska uprava Nova Varoš","parent_id":"9057","grupisanje":"lokal"},{"id":"9083","naziv":"Predsednik opštine Požega","parent_id":"9085","grupisanje":"lokal"},{"id":"9084","naziv":"Opštinsko veće Požega","parent_id":"9085","grupisanje":"lokal"},{"id":"9085","naziv":"Skupština opštine Požega","parent_id":"15001","grupisanje":"lokal"},{"id":"9086","naziv":"Opštinska uprava Požega","parent_id":"9084","grupisanje":"lokal"},{"id":"9136","naziv":"Predsednik opštine Priboj","parent_id":"9138","grupisanje":"lokal"},{"id":"9137","naziv":"Opštinsko veće Priboj","parent_id":"9138","grupisanje":"lokal"},{"id":"9138","naziv":"Skupština opštine Priboj","parent_id":"15001","grupisanje":"lokal"},{"id":"9139","naziv":"Opštinska uprava Priboj","parent_id":"9137","grupisanje":"lokal"},{"id":"9166","naziv":"Predsednik opštine Prijepolje","parent_id":"9168","grupisanje":"lokal"},{"id":"9167","naziv":"Opštinsko veće Prijepolje","parent_id":"9168","grupisanje":"lokal"},{"id":"9168","naziv":"Skupština opštine Prijepolje","parent_id":"15001","grupisanje":"lokal"},{"id":"9169","naziv":"Opštinska uprava Prijepolje","parent_id":"9167","grupisanje":"lokal"},{"id":"9210","naziv":"Predsednik opštine Sjenica","parent_id":"9212","grupisanje":"lokal"},{"id":"9211","naziv":"Opštinsko veće Sjenica","parent_id":"9212","grupisanje":"lokal"},{"id":"9212","naziv":"Skupština opštine Sjenica","parent_id":"15001","grupisanje":"lokal"},{"id":"9213","naziv":"Opštinska uprava Sjenica","parent_id":"9211","grupisanje":"lokal"},{"id":"9235","naziv":"Predsednik opštine Čajetina","parent_id":"9237","grupisanje":"lokal"},{"id":"9236","naziv":"Opštinsko veće Čajetina","parent_id":"9237","grupisanje":"lokal"},{"id":"9237","naziv":"Skupština opštine Čajetina","parent_id":"15001","grupisanje":"lokal"},{"id":"9238","naziv":"Opštinska uprava Čajetina","parent_id":"9236","grupisanje":"lokal"},{"id":"9269","naziv":"Predsednik opštine Gornji Milanovac","parent_id":"9271","grupisanje":"lokal"},{"id":"9270","naziv":"Opštinsko veće Gornji Milanovac","parent_id":"9271","grupisanje":"lokal"},{"id":"9271","naziv":"Skupština opštine Gornji Milanovac","parent_id":"15001","grupisanje":"lokal"},{"id":"9272","naziv":"Opštinska uprava Gornji Milanovac","parent_id":"9270","grupisanje":"lokal"},{"id":"9336","naziv":"Predsednik opštine Ivanjica","parent_id":"9338","grupisanje":"lokal"},{"id":"9337","naziv":"Opštinsko veće Ivanjica","parent_id":"9338","grupisanje":"lokal"},{"id":"9338","naziv":"Skupština opštine Ivanjica","parent_id":"15001","grupisanje":"lokal"},{"id":"9339","naziv":"Opštinska uprava Ivanjica","parent_id":"9337","grupisanje":"lokal"},{"id":"9367","naziv":"Predsednik opštine Lučani","parent_id":"9369","grupisanje":"lokal"},{"id":"9368","naziv":"Opštinsko veće Lučani","parent_id":"9369","grupisanje":"lokal"},{"id":"9369","naziv":"Skupština opštine Lučani","parent_id":"15001","grupisanje":"lokal"},{"id":"9370","naziv":"Opštinska uprava Lučani","parent_id":"9368","grupisanje":"lokal"},{"id":"9408","naziv":"Predsednik opštine Vrnjačka Banja","parent_id":"9410","grupisanje":"lokal"},{"id":"9409","naziv":"Opštinsko veće Vrnjačka Banja","parent_id":"9410","grupisanje":"lokal"},{"id":"9410","naziv":"Skupština opštine Vrnjačka Banja","parent_id":"15001","grupisanje":"lokal"},{"id":"9411","naziv":"Opštinska uprava Vrnjačka Banja","parent_id":"9409","grupisanje":"lokal"},{"id":"9436","naziv":"Predsednik opštine Raška","parent_id":"9438","grupisanje":"lokal"},{"id":"9437","naziv":"Opštinsko veće Raška","parent_id":"9438","grupisanje":"lokal"},{"id":"9438","naziv":"Skupština opštine Raška","parent_id":"15001","grupisanje":"lokal"},{"id":"9439","naziv":"Opštinska uprava Raška","parent_id":"9437","grupisanje":"lokal"},{"id":"9468","naziv":"Predsednik opštine Tutin","parent_id":"9470","grupisanje":"lokal"},{"id":"9469","naziv":"Opštinsko veće Tutin","parent_id":"9470","grupisanje":"lokal"},{"id":"9470","naziv":"Skupština opštine Tutin","parent_id":"15001","grupisanje":"lokal"},{"id":"9471","naziv":"Opštinska uprava Tutin","parent_id":"9469","grupisanje":"lokal"},{"id":"9509","naziv":"Predsednik opštine Aleksandrovac","parent_id":"9511","grupisanje":"lokal"},{"id":"9510","naziv":"Opštinsko veće Aleksandrovac","parent_id":"9511","grupisanje":"lokal"},{"id":"9511","naziv":"Skupština opštine Aleksandrovac","parent_id":"15001","grupisanje":"lokal"},{"id":"9512","naziv":"Opštinska uprava Aleksandrovac","parent_id":"9510","grupisanje":"lokal"},{"id":"9541","naziv":"Predsednik opštine Brus","parent_id":"9543","grupisanje":"lokal"},{"id":"9542","naziv":"Opštinsko veće Brus","parent_id":"9543","grupisanje":"lokal"},{"id":"9543","naziv":"Skupština opštine Brus","parent_id":"15001","grupisanje":"lokal"},{"id":"9544","naziv":"Opštinska uprava Brus","parent_id":"9542","grupisanje":"lokal"},{"id":"9594","naziv":"Predsednik opštine Varvarin","parent_id":"9596","grupisanje":"lokal"},{"id":"9595","naziv":"Opštinsko veće Varvarin","parent_id":"9596","grupisanje":"lokal"},{"id":"9596","naziv":"Skupština opštine Varvarin","parent_id":"15001","grupisanje":"lokal"},{"id":"9597","naziv":"Opštinska uprava Varvarin","parent_id":"9595","grupisanje":"lokal"},{"id":"9632","naziv":"Predsednik opštine Trstenik","parent_id":"9634","grupisanje":"lokal"},{"id":"9633","naziv":"Opštinsko veće Trstenik","parent_id":"9634","grupisanje":"lokal"},{"id":"9634","naziv":"Skupština opštine Trstenik","parent_id":"15001","grupisanje":"lokal"},{"id":"9635","naziv":"Opštinska uprava Trstenik","parent_id":"9633","grupisanje":"lokal"},{"id":"9687","naziv":"Predsednik opštine Ćićevac","parent_id":"9689","grupisanje":"lokal"},{"id":"9688","naziv":"Opštinsko veće Ćićevac","parent_id":"9689","grupisanje":"lokal"},{"id":"9689","naziv":"Skupština opštine Ćićevac","parent_id":"15001","grupisanje":"lokal"},{"id":"9690","naziv":"Opštinska uprava Ćićevac","parent_id":"9688","grupisanje":"lokal"},{"id":"9711","naziv":"Predsednik opštine Aleksinac","parent_id":"9713","grupisanje":"lokal"},{"id":"9712","naziv":"Opštinsko veće Aleksinac","parent_id":"9713","grupisanje":"lokal"},{"id":"9713","naziv":"Skupština opštine Aleksinac","parent_id":"15001","grupisanje":"lokal"},{"id":"9714","naziv":"Opštinska uprava Aleksinac","parent_id":"9712","grupisanje":"lokal"},{"id":"9801","naziv":"Predsednik opštine Gadžin Han","parent_id":"9803","grupisanje":"lokal"},{"id":"9802","naziv":"Opštinsko veće Gadžin Han","parent_id":"9803","grupisanje":"lokal"},{"id":"9803","naziv":"Skupština opštine Gadžin Han","parent_id":"15001","grupisanje":"lokal"},{"id":"9804","naziv":"Opštinska uprava Gadžin Han","parent_id":"9802","grupisanje":"lokal"},{"id":"9837","naziv":"Predsednik opštine Doljevac","parent_id":"9839","grupisanje":"lokal"},{"id":"9838","naziv":"Opštinsko veće Doljevac","parent_id":"9839","grupisanje":"lokal"},{"id":"9839","naziv":"Skupština opštine Doljevac","parent_id":"15001","grupisanje":"lokal"},{"id":"9840","naziv":"Opštinska uprava Doljevac","parent_id":"9838","grupisanje":"lokal"},{"id":"9864","naziv":"Predsednik opštine Merošina","parent_id":"9866","grupisanje":"lokal"},{"id":"9865","naziv":"Opštinsko veće Merošina","parent_id":"9866","grupisanje":"lokal"},{"id":"9866","naziv":"Skupština opštine Merošina","parent_id":"15001","grupisanje":"lokal"},{"id":"9867","naziv":"Opštinska uprava Merošina","parent_id":"9865","grupisanje":"lokal"},{"id":"9903","naziv":"Predsednik opštine Ražanj","parent_id":"9905","grupisanje":"lokal"},{"id":"9904","naziv":"Opštinsko veće Ražanj","parent_id":"9905","grupisanje":"lokal"},{"id":"9905","naziv":"Skupština opštine Ražanj","parent_id":"15001","grupisanje":"lokal"},{"id":"9906","naziv":"Opštinska uprava Ražanj","parent_id":"9904","grupisanje":"lokal"},{"id":"9936","naziv":"Predsednik opštine Svrljig","parent_id":"9938","grupisanje":"lokal"},{"id":"9937","naziv":"Opštinsko veće Svrljig","parent_id":"9938","grupisanje":"lokal"},{"id":"9938","naziv":"Skupština opštine Svrljig","parent_id":"15001","grupisanje":"lokal"},{"id":"9939","naziv":"Opštinska uprava Svrljig","parent_id":"9937","grupisanje":"lokal"},{"id":"9984","naziv":"Predsednik opštine Blace","parent_id":"9986","grupisanje":"lokal"},{"id":"9985","naziv":"Opštinsko veće Blace","parent_id":"9986","grupisanje":"lokal"},{"id":"9986","naziv":"Skupština opštine Blace","parent_id":"15001","grupisanje":"lokal"},{"id":"9987","naziv":"Opštinska uprava Blace","parent_id":"9985","grupisanje":"lokal"},{"id":"10025","naziv":"Predsednik opštine Žitorađa","parent_id":"10027","grupisanje":"lokal"},{"id":"10026","naziv":"Opštinsko veće Žitorađa","parent_id":"10027","grupisanje":"lokal"},{"id":"10027","naziv":"Skupština opštine Žitorađa","parent_id":"15001","grupisanje":"lokal"},{"id":"10028","naziv":"Opštinska uprava Žitorađa","parent_id":"10026","grupisanje":"lokal"},{"id":"10065","naziv":"Predsednik opštine Kuršumlija","parent_id":"10067","grupisanje":"lokal"},{"id":"10066","naziv":"Opštinsko veće Kuršumlija","parent_id":"10067","grupisanje":"lokal"},{"id":"10067","naziv":"Skupština opštine Kuršumlija","parent_id":"15001","grupisanje":"lokal"},{"id":"10068","naziv":"Opštinska uprava Kuršumlija","parent_id":"10066","grupisanje":"lokal"},{"id":"10221","naziv":"Predsednik opštine Babušnica","parent_id":"10223","grupisanje":"lokal"},{"id":"10222","naziv":"Opštinsko veće Babušnica","parent_id":"10223","grupisanje":"lokal"},{"id":"10223","naziv":"Skupština opštine Babušnica","parent_id":"15001","grupisanje":"lokal"},{"id":"10224","naziv":"Opštinska uprava Babušnica","parent_id":"10222","grupisanje":"lokal"},{"id":"10286","naziv":"Predsednik opštine Bela Palanka","parent_id":"10288","grupisanje":"lokal"},{"id":"10287","naziv":"Opštinsko veće Bela Palanka","parent_id":"10288","grupisanje":"lokal"},{"id":"10288","naziv":"Skupština opštine Bela Palanka","parent_id":"15001","grupisanje":"lokal"},{"id":"10289","naziv":"Opštinska uprava Bela Palanka","parent_id":"10287","grupisanje":"lokal"},{"id":"10345","naziv":"Predsednik opštine Dimitrovgrad","parent_id":"10347","grupisanje":"lokal"},{"id":"10346","naziv":"Opštinsko veće Dimitrovgrad","parent_id":"10347","grupisanje":"lokal"},{"id":"10347","naziv":"Skupština opštine Dimitrovgrad","parent_id":"15001","grupisanje":"lokal"},{"id":"10348","naziv":"Opštinska uprava Dimitrovgrad","parent_id":"10346","grupisanje":"lokal"},{"id":"10402","naziv":"Predsednik opštine Bojnik","parent_id":"10404","grupisanje":"lokal"},{"id":"10403","naziv":"Opštinsko veće Bojnik","parent_id":"10404","grupisanje":"lokal"},{"id":"10404","naziv":"Skupština opštine Bojnik","parent_id":"15001","grupisanje":"lokal"},{"id":"10405","naziv":"Opštinska uprava Bojnik","parent_id":"10403","grupisanje":"lokal"},{"id":"10451","naziv":"Predsednik opštine Vlasotince","parent_id":"10453","grupisanje":"lokal"},{"id":"10452","naziv":"Opštinsko veće Vlasotince","parent_id":"10453","grupisanje":"lokal"},{"id":"10453","naziv":"Skupština opštine Vlasotince","parent_id":"15001","grupisanje":"lokal"},{"id":"10454","naziv":"Opštinska uprava Vlasotince","parent_id":"10452","grupisanje":"lokal"},{"id":"10519","naziv":"Predsednik opštine Lebane","parent_id":"10521","grupisanje":"lokal"},{"id":"10520","naziv":"Opštinsko veće Lebane","parent_id":"10521","grupisanje":"lokal"},{"id":"10521","naziv":"Skupština opštine Lebane","parent_id":"15001","grupisanje":"lokal"},{"id":"10522","naziv":"Opštinska uprava Lebane","parent_id":"10520","grupisanje":"lokal"},{"id":"10570","naziv":"Predsednik opštine Medveđa","parent_id":"10572","grupisanje":"lokal"},{"id":"10571","naziv":"Opštinsko veće Medveđa","parent_id":"10572","grupisanje":"lokal"},{"id":"10572","naziv":"Skupština opštine Medveđa","parent_id":"15001","grupisanje":"lokal"},{"id":"10573","naziv":"Opštinska uprava Medveđa","parent_id":"10571","grupisanje":"lokal"},{"id":"10615","naziv":"Predsednik opštine Crna Trava","parent_id":"10617","grupisanje":"lokal"},{"id":"10616","naziv":"Opštinsko veće Crna Trava","parent_id":"10617","grupisanje":"lokal"},{"id":"10617","naziv":"Skupština opštine Crna Trava","parent_id":"15001","grupisanje":"lokal"},{"id":"10618","naziv":"Opštinska uprava Crna Trava","parent_id":"10616","grupisanje":"lokal"},{"id":"10639","naziv":"Predsednik opštine Bosilegrad","parent_id":"10641","grupisanje":"lokal"},{"id":"10640","naziv":"Opštinsko veće Bosilegrad","parent_id":"10641","grupisanje":"lokal"},{"id":"10641","naziv":"Skupština opštine Bosilegrad","parent_id":"15001","grupisanje":"lokal"},{"id":"10642","naziv":"Opštinska uprava Bosilegrad","parent_id":"10640","grupisanje":"lokal"},{"id":"10687","naziv":"Predsednik opštine Bujanovac","parent_id":"10689","grupisanje":"lokal"},{"id":"10688","naziv":"Opštinsko veće Bujanovac","parent_id":"10689","grupisanje":"lokal"},{"id":"10689","naziv":"Skupština opštine Bujanovac","parent_id":"15001","grupisanje":"lokal"},{"id":"10690","naziv":"Opštinska uprava Bujanovac","parent_id":"10688","grupisanje":"lokal"},{"id":"10727","naziv":"Predsednik opštine Vladičin Han","parent_id":"10729","grupisanje":"lokal"},{"id":"10728","naziv":"Opštinsko veće Vladičin Han","parent_id":"10729","grupisanje":"lokal"},{"id":"10729","naziv":"Skupština opštine Vladičin Han","parent_id":"15001","grupisanje":"lokal"},{"id":"10730","naziv":"Opštinska uprava Vladičin Han","parent_id":"10728","grupisanje":"lokal"},{"id":"10798","naziv":"Predsednik opštine Preševo","parent_id":"10800","grupisanje":"lokal"},{"id":"10799","naziv":"Opštinsko veće Preševo","parent_id":"10800","grupisanje":"lokal"},{"id":"10800","naziv":"Skupština opštine Preševo","parent_id":"15001","grupisanje":"lokal"},{"id":"10801","naziv":"Opštinska uprava Preševo","parent_id":"10799","grupisanje":"lokal"},{"id":"10835","naziv":"Predsednik opštine Surdulica","parent_id":"10837","grupisanje":"lokal"},{"id":"10836","naziv":"Opštinsko veće Surdulica","parent_id":"10837","grupisanje":"lokal"},{"id":"10837","naziv":"Skupština opštine Surdulica","parent_id":"15001","grupisanje":"lokal"},{"id":"10877","naziv":"Predsednik opštine Trgovište","parent_id":"10879","grupisanje":"lokal"},{"id":"10878","naziv":"Opštinsko veće Trgovište","parent_id":"10879","grupisanje":"lokal"},{"id":"10879","naziv":"Skupština opštine Trgovište","parent_id":"15001","grupisanje":"lokal"},{"id":"10880","naziv":"Opštinska uprava Trgovište","parent_id":"10878","grupisanje":"lokal"},{"id":"10894","naziv":"Predsednik opštine Glogovac","parent_id":"10896","grupisanje":"lokal"},{"id":"10895","naziv":"Opštinsko veće Glogovac","parent_id":"10896","grupisanje":"lokal"},{"id":"10896","naziv":"Skupština opštine Glogovac","parent_id":"15001","grupisanje":"lokal"},{"id":"10897","naziv":"Opštinska uprava Glogovac","parent_id":"10895","grupisanje":"lokal"},{"id":"10898","naziv":"Predsednik opštine Kačanik","parent_id":"10900","grupisanje":"lokal"},{"id":"10899","naziv":"Opštinsko veće Kačanik","parent_id":"10900","grupisanje":"lokal"},{"id":"10900","naziv":"Skupština opštine Kačanik","parent_id":"15001","grupisanje":"lokal"},{"id":"10901","naziv":"Opštinska uprava Kačanik","parent_id":"10899","grupisanje":"lokal"},{"id":"10902","naziv":"Predsednik opštine Kosovo Polje","parent_id":"10904","grupisanje":"lokal"},{"id":"10903","naziv":"Opštinsko veće Kosovo Polje","parent_id":"10904","grupisanje":"lokal"},{"id":"10904","naziv":"Skupština opštine Kosovo Polje","parent_id":"15001","grupisanje":"lokal"},{"id":"10905","naziv":"Opštinska uprava Kosovo Polje","parent_id":"10903","grupisanje":"lokal"},{"id":"10906","naziv":"Predsednik opštine Lipljan","parent_id":"10908","grupisanje":"lokal"},{"id":"10907","naziv":"Opštinsko veće Lipljan","parent_id":"10908","grupisanje":"lokal"},{"id":"10908","naziv":"Skupština opštine Lipljan","parent_id":"15001","grupisanje":"lokal"},{"id":"10909","naziv":"Opštinska uprava Lipljan","parent_id":"10907","grupisanje":"lokal"},{"id":"10910","naziv":"Predsednik opštine Obilić","parent_id":"10912","grupisanje":"lokal"},{"id":"10911","naziv":"Opštinsko veće Obilić","parent_id":"10912","grupisanje":"lokal"},{"id":"10912","naziv":"Skupština opštine Obilić","parent_id":"15001","grupisanje":"lokal"},{"id":"10913","naziv":"Opštinska uprava Obilić","parent_id":"10911","grupisanje":"lokal"},{"id":"10914","naziv":"Predsednik opštine Podujevo","parent_id":"10916","grupisanje":"lokal"},{"id":"10915","naziv":"Opštinsko veće Podujevo","parent_id":"10916","grupisanje":"lokal"},{"id":"10916","naziv":"Skupština opštine Podujevo","parent_id":"15001","grupisanje":"lokal"},{"id":"10917","naziv":"Opštinska uprava Podujevo","parent_id":"10915","grupisanje":"lokal"},{"id":"10918","naziv":"Predsednik opštine Uroševac","parent_id":"10920","grupisanje":"lokal"},{"id":"10919","naziv":"Opštinsko veće Uroševac","parent_id":"10920","grupisanje":"lokal"},{"id":"10920","naziv":"Skupština opštine Uroševac","parent_id":"15001","grupisanje":"lokal"},{"id":"10921","naziv":"Opštinska uprava Uroševac","parent_id":"10919","grupisanje":"lokal"},{"id":"10922","naziv":"Predsednik opštine Štimlje","parent_id":"10924","grupisanje":"lokal"},{"id":"10923","naziv":"Opštinsko veće Štimlje","parent_id":"10924","grupisanje":"lokal"},{"id":"10924","naziv":"Skupština opštine Štimlje","parent_id":"15001","grupisanje":"lokal"},{"id":"10925","naziv":"Opštinska uprava Štimlje","parent_id":"10923","grupisanje":"lokal"},{"id":"10926","naziv":"Predsednik opštine Štrpce","parent_id":"10928","grupisanje":"lokal"},{"id":"10927","naziv":"Opštinsko veće Štrpce","parent_id":"10928","grupisanje":"lokal"},{"id":"10928","naziv":"Skupština opštine Štrpce","parent_id":"15001","grupisanje":"lokal"},{"id":"10929","naziv":"Opštinska uprava Štrpce","parent_id":"10927","grupisanje":"lokal"},{"id":"10935","naziv":"Predsednik opštine Dečani","parent_id":"10937","grupisanje":"lokal"},{"id":"10936","naziv":"Opštinsko veće Dečani","parent_id":"10937","grupisanje":"lokal"},{"id":"10937","naziv":"Skupština opštine Dečani","parent_id":"15001","grupisanje":"lokal"},{"id":"10938","naziv":"Opštinska uprava Dečani","parent_id":"10936","grupisanje":"lokal"},{"id":"10939","naziv":"Predsednik opštine Đakovica","parent_id":"10941","grupisanje":"lokal"},{"id":"10940","naziv":"Opštinsko veće Đakovica","parent_id":"10941","grupisanje":"lokal"},{"id":"10941","naziv":"Skupština opštine Đakovica","parent_id":"15001","grupisanje":"lokal"},{"id":"10942","naziv":"Opštinska uprava Đakovica","parent_id":"10940","grupisanje":"lokal"},{"id":"10943","naziv":"Predsednik opštine Istok","parent_id":"10945","grupisanje":"lokal"},{"id":"10944","naziv":"Opštinsko veće Istok","parent_id":"10945","grupisanje":"lokal"},{"id":"10945","naziv":"Skupština opštine Istok","parent_id":"15001","grupisanje":"lokal"},{"id":"10946","naziv":"Opštinska uprava Istok","parent_id":"10944","grupisanje":"lokal"},{"id":"10947","naziv":"Predsednik opštine Klina","parent_id":"10949","grupisanje":"lokal"},{"id":"10948","naziv":"Opštinsko veće Klina","parent_id":"10949","grupisanje":"lokal"},{"id":"10949","naziv":"Skupština opštine Klina","parent_id":"15001","grupisanje":"lokal"},{"id":"10950","naziv":"Opštinska uprava Klina","parent_id":"10948","grupisanje":"lokal"},{"id":"10951","naziv":"Predsednik opštine Peć","parent_id":"10953","grupisanje":"lokal"},{"id":"10952","naziv":"Opštinsko veće Peć","parent_id":"10953","grupisanje":"lokal"},{"id":"10953","naziv":"Skupština opštine Peć","parent_id":"15001","grupisanje":"lokal"},{"id":"10954","naziv":"Opštinska uprava Peć","parent_id":"10952","grupisanje":"lokal"},{"id":"10955","naziv":"Predsednik opštine Gora","parent_id":"10957","grupisanje":"lokal"},{"id":"10956","naziv":"Opštinsko veće Gora","parent_id":"10957","grupisanje":"lokal"},{"id":"10957","naziv":"Skupština opštine Gora","parent_id":"15001","grupisanje":"lokal"},{"id":"10958","naziv":"Opštinska uprava Gora","parent_id":"10956","grupisanje":"lokal"},{"id":"10959","naziv":"Predsednik opštine Orahovac","parent_id":"10961","grupisanje":"lokal"},{"id":"10960","naziv":"Opštinsko veće Orahovac","parent_id":"10961","grupisanje":"lokal"},{"id":"10961","naziv":"Skupština opštine Orahovac","parent_id":"15001","grupisanje":"lokal"},{"id":"10962","naziv":"Opštinska uprava Orahovac","parent_id":"10960","grupisanje":"lokal"},{"id":"10963","naziv":"Predsednik opštine Prizren","parent_id":"10965","grupisanje":"lokal"},{"id":"10964","naziv":"Opštinsko veće Prizren","parent_id":"10965","grupisanje":"lokal"},{"id":"10965","naziv":"Skupština opštine Prizren","parent_id":"15001","grupisanje":"lokal"},{"id":"10966","naziv":"Opštinska uprava Prizren","parent_id":"10964","grupisanje":"lokal"},{"id":"10967","naziv":"Predsednik opštine Suva Reka","parent_id":"10969","grupisanje":"lokal"},{"id":"10968","naziv":"Opštinsko veće Suva Reka","parent_id":"10969","grupisanje":"lokal"},{"id":"10969","naziv":"Skupština opštine Suva Reka","parent_id":"15001","grupisanje":"lokal"},{"id":"10970","naziv":"Opštinska uprava Suva Reka","parent_id":"10968","grupisanje":"lokal"},{"id":"10971","naziv":"Predsednik opštine Vučitrn","parent_id":"10973","grupisanje":"lokal"},{"id":"10972","naziv":"Opštinsko veće Vučitrn","parent_id":"10973","grupisanje":"lokal"},{"id":"10973","naziv":"Skupština opštine Vučitrn","parent_id":"15001","grupisanje":"lokal"},{"id":"10974","naziv":"Opštinska uprava Vučitrn","parent_id":"10972","grupisanje":"lokal"},{"id":"10975","naziv":"Predsednik opštine Zvečan","parent_id":"10977","grupisanje":"lokal"},{"id":"10976","naziv":"Opštinsko veće Zvečan","parent_id":"10977","grupisanje":"lokal"},{"id":"10977","naziv":"Skupština opštine Zvečan","parent_id":"15001","grupisanje":"lokal"},{"id":"10978","naziv":"Opštinska uprava Zvečan","parent_id":"10976","grupisanje":"lokal"},{"id":"10979","naziv":"Predsednik opštine Zubin Potok","parent_id":"10981","grupisanje":"lokal"},{"id":"10980","naziv":"Opštinsko veće Zubin Potok","parent_id":"10981","grupisanje":"lokal"},{"id":"10981","naziv":"Skupština opštine Zubin Potok","parent_id":"15001","grupisanje":"lokal"},{"id":"10982","naziv":"Opštinska uprava Zubin Potok","parent_id":"10980","grupisanje":"lokal"},{"id":"10996","naziv":"Predsednik opštine Kosovska Mitrovica","parent_id":"10998","grupisanje":"lokal"},{"id":"10997","naziv":"Opštinsko veće Kosovska Mitrovica","parent_id":"10998","grupisanje":"lokal"},{"id":"10998","naziv":"Skupština opštine Kosovska Mitrovica","parent_id":"15001","grupisanje":"lokal"},{"id":"10999","naziv":"Opštinska uprava Kosovska Mitrovica","parent_id":"10997","grupisanje":"lokal"},{"id":"11000","naziv":"Opštinski pravobranilac","parent_id":"10998","grupisanje":"lokal"},{"id":"11032","naziv":"Predsednik opštine Leposavić","parent_id":"11034","grupisanje":"lokal"},{"id":"11033","naziv":"Opštinsko veće Leposavić","parent_id":"11034","grupisanje":"lokal"},{"id":"11034","naziv":"Skupština opštine Leposavić","parent_id":"15001","grupisanje":"lokal"},{"id":"11035","naziv":"Opštinska uprava Leposavić","parent_id":"11033","grupisanje":"lokal"},{"id":"11053","naziv":"Predsednik opštine Srbica","parent_id":"11055","grupisanje":"lokal"},{"id":"11054","naziv":"Opštinsko veće Srbica","parent_id":"11055","grupisanje":"lokal"},{"id":"11055","naziv":"Skupština opštine Srbica","parent_id":"15001","grupisanje":"lokal"},{"id":"11056","naziv":"Opštinska uprava Srbica","parent_id":"11054","grupisanje":"lokal"},{"id":"11057","naziv":"Predsednik opštine Vitina","parent_id":"11059","grupisanje":"lokal"},{"id":"11058","naziv":"Opštinsko veće Vitina","parent_id":"11059","grupisanje":"lokal"},{"id":"11059","naziv":"Skupština opštine Vitina","parent_id":"15001","grupisanje":"lokal"},{"id":"11060","naziv":"Opštinska uprava Vitina","parent_id":"11058","grupisanje":"lokal"},{"id":"11061","naziv":"Predsednik opštine Gnjilane","parent_id":"11063","grupisanje":"lokal"},{"id":"11062","naziv":"Opštinsko veće Gnjilane","parent_id":"11063","grupisanje":"lokal"},{"id":"11063","naziv":"Skupština opštine Gnjilane","parent_id":"15001","grupisanje":"lokal"},{"id":"11064","naziv":"Opštinska uprava Gnjilane","parent_id":"11062","grupisanje":"lokal"},{"id":"11073","naziv":"Predsednik opštine Kosovska Kamenica","parent_id":"11075","grupisanje":"lokal"},{"id":"11074","naziv":"Opštinsko veće Kosovska Kamenica","parent_id":"11075","grupisanje":"lokal"},{"id":"11075","naziv":"Skupština opštine Kosovska Kamenica","parent_id":"15001","grupisanje":"lokal"},{"id":"11076","naziv":"Opštinska uprava Kosovska Kamenica","parent_id":"11074","grupisanje":"lokal"},{"id":"11077","naziv":"Predsednik opštine Novo Brdo","parent_id":"11079","grupisanje":"lokal"},{"id":"11078","naziv":"Opštinsko veće Novo Brdo","parent_id":"11079","grupisanje":"lokal"},{"id":"11079","naziv":"Skupština opštine Novo Brdo","parent_id":"15001","grupisanje":"lokal"},{"id":"11080","naziv":"Opštinska uprava Novo Brdo","parent_id":"11078","grupisanje":"lokal"},{"id":"1","naziv":"Narodna skupština Republike Srbije","parent_id":"0","grupisanje":"najvisi"},{"id":"10","naziv":"Agencija za borbu protiv korupcije","parent_id":"m","grupisanje":"nezavisni"},{"id":"100","naziv":"Republički sekretarijat za zakonodavstvo","parent_id":"3","grupisanje":"posebni"},{"id":"101","naziv":"Republički sekretarijat za javne politike","parent_id":"3","grupisanje":"posebni"},{"id":"102","naziv":"Komesarijat za izbeglice i migracije","parent_id":"3","grupisanje":"posebni"},{"id":"103","naziv":"Centar za razminiranje","parent_id":"3","grupisanje":"posebni"},{"id":"104","naziv":"Akreditaciono telo Srbije","parent_id":"m","grupisanje":"posebni"},{"id":"105","naziv":"Nacionalna služba za zapošljavanje","parent_id":"m","grupisanje":"posebni"},{"id":"106","naziv":"Inspektorat odbrane (organ uprave u sastavu Ministarstva odbrane)","parent_id":"28","grupisanje":"posebni"},{"id":"107","naziv":"Uprava carina (organ uprave u sastavu Ministarstva finansija)","parent_id":"30","grupisanje":"posebni"},{"id":"108","naziv":"Poreska uprava (organ uprave u sastavu Ministarstva finansija)","parent_id":"30","grupisanje":"posebni"},{"id":"109","naziv":"Uprava za trezor (organ uprave u sastavu Ministarstva finansija)","parent_id":"30","grupisanje":"posebni"},{"id":"11","naziv":"Poverenik za informacije od javnog značaja i zaštitu podataka o ličnosti","parent_id":"1","grupisanje":"nezavisni"},{"id":"110","naziv":"Uprava za javni dug (organ uprave u sastavu Ministarstva finansija)","parent_id":"30","grupisanje":"posebni"},{"id":"111","naziv":"Uprava za duvan (organ uprave u sastavu Ministarstva finansija)","parent_id":"30","grupisanje":"posebni"},{"id":"112","naziv":"Uprava za sprečavanje pranja novca (organ uprave u sastavu Ministarstva finansija)","parent_id":"30","grupisanje":"posebni"},{"id":"113","naziv":"Uprava za slobodne zone (organ uprave u sastavu Ministarstva finansija)","parent_id":"30","grupisanje":"posebni"},{"id":"114","naziv":"Uprava za izvršenje krivičnih sankcija (organ uprave u sastavu Ministarstva pravde)","parent_id":"32","grupisanje":"posebni"},{"id":"115","naziv":"Uprava za veterinu (organ uprave u sastavu Ministarstva poljoprivrede, šumarstva i vodoprivrede)","parent_id":"33","grupisanje":"posebni"},{"id":"116","naziv":"Uprava za zaštitu bilja (organ uprave u sastavu Ministarstva poljoprivrede, šumarstva i vodoprivrede)","parent_id":"33","grupisanje":"posebni"},{"id":"117","naziv":"Uprava za šume (organ uprave u sastavu Ministarstva poljoprivrede, šumarstva i vodoprivrede)","parent_id":"33","grupisanje":"posebni"},{"id":"118","naziv":"Upravni inspektorat (organ uprave u sastavu Ministarstva državne uprave i lokalne samouprave)","parent_id":"36","grupisanje":"posebni"},{"id":"119","naziv":"Uprava za agrarna plaćanja (organ uprave u sastavu Ministarstva poljoprivrede i zaštite životne sredine)","parent_id":"33","grupisanje":"posebni"},{"id":"12","naziv":"Poverenik za zaštitu ravnopravnosti","parent_id":"1","grupisanje":"nezavisni"},{"id":"120","naziv":"Uprava za poljoprivredna zemljišta (organ uprave u sastavu Ministarstva poljoprivrede i zaštite životne sredine)","parent_id":"33","grupisanje":"posebni"},{"id":"121","naziv":"Uprava za bezbednost i zdravlje na radu (organ uprave u sastavu Ministarstva za rad, zapošljavanje, boračka i socijalna pitanja)","parent_id":"43","grupisanje":"posebni"},{"id":"122","naziv":"Uprava za saradnju sa dijasporom i Srbima u regionu (organ uprave u sastavu Ministarstva spoljnih poslova)","parent_id":"27","grupisanje":"posebni"},{"id":"123","naziv":"Uprava za saradnju sa crkvenim i verskim zajednicama (organ uprave u sastavu Ministarstva pravde)","parent_id":"32","grupisanje":"posebni"},{"id":"124","naziv":"Inspektorat za rad (organ uprave u sastavu Ministarstva za rad, zapošljavanje, boračka i socijalna pitanja)","parent_id":"43","grupisanje":"posebni"},{"id":"125","naziv":"Pravosudna akademija","parent_id":"m","grupisanje":"posebni"},{"id":"126","naziv":"Direktorat civilnog vazduhoplovstva Republike Srbije","parent_id":"36","grupisanje":"posebni"},{"id":"127","naziv":"Kontrola letenja Srbije i Crne Gore (SMATSA doo)","parent_id":"37","grupisanje":"posebni"},{"id":"128","naziv":"Centralni registar obaveznog socijalnog osiguranja","parent_id":"m","grupisanje":"posebni"},{"id":"129","naziv":"Turistička organizacija Srbije","parent_id":"39","grupisanje":"posebni"},{"id":"13","naziv":"Visoki savet sudstva","parent_id":"m","grupisanje":"nezavisni"},{"id":"130","naziv":"Nacionalna korporacija za osiguranje stambenih kredita","parent_id":"30","grupisanje":"posebni"},{"id":"131","naziv":"Nacionalni savet ukrajinske nacionalne zajednice","parent_id":"15000","grupisanje":"nacionalni_savet"},{"id":"132","naziv":"Nacionalni savet romske nacionalne zajednice","parent_id":"15000","grupisanje":"nacionalni_savet"},{"id":"15000","naziv":"Nacionalni savet","parent_id":"0","grupisanje":"nacionalni_savet"},{"id":"133","naziv":"Nacionalni savet bunjevačke nacionalne zajednice","parent_id":"15000","grupisanje":"nacionalni_savet"},{"id":"134","naziv":"Nacionalni savet madjarske nacionalne zajednice","parent_id":"15000","grupisanje":"nacionalni_savet"},{"id":"135","naziv":"Nacionalni savet bugarske nacionalne zajednice","parent_id":"15000","grupisanje":"nacionalni_savet"},{"id":"136","naziv":"Nacionalni savet bošnjačke nacionalne manjine","parent_id":"15000","grupisanje":"nacionalni_savet"},{"id":"137","naziv":"Nacionalni savet rumunske nacionalne zajednice","parent_id":"15000","grupisanje":"nacionalni_savet"},{"id":"138","naziv":"Nacionalni savet slovačke nacionalne zajednice","parent_id":"15000","grupisanje":"nacionalni_savet"},{"id":"139","naziv":"Nacionalni savet hrvatske nacionalne manjine","parent_id":"15000","grupisanje":"nacionalni_savet"},{"id":"14","naziv":"Državno veće tužilaca","parent_id":"m","grupisanje":"nezavisni"},{"id":"140","naziv":"Nacionalni savet rusinske nacionalne zajednice","parent_id":"15000","grupisanje":"nacionalni_savet"},{"id":"141","naziv":"Nacionalni savet vlaške nacionalne manjine","parent_id":"15000","grupisanje":"nacionalni_savet"},{"id":"142","naziv":"Nacionalni savet grčke nacionalne manjine","parent_id":"15000","grupisanje":"nacionalni_savet"},{"id":"143","naziv":"Nacionalni savete egipatske nacionalne manjine","parent_id":"15000","grupisanje":"nacionalni_savet"},{"id":"144","naziv":"Nacionalni savet nemačke nacionalne manjine","parent_id":"15000","grupisanje":"nacionalni_savet"},{"id":"145","naziv":"Nacionalni savet slovenačke nacionalne manjine","parent_id":"15000","grupisanje":"nacionalni_savet"},{"id":"146","naziv":"Nacionalni savet češke nacionalne manjine","parent_id":"15000","grupisanje":"nacionalni_savet"},{"id":"147","naziv":"Nacionalni savet makedonske nacionalne manjine","parent_id":"15000","grupisanje":"nacionalni_savet"},{"id":"148","naziv":"Nacionalni savet albanske nacionalne manjine","parent_id":"15000","grupisanje":"nacionalni_savet"},{"id":"149","naziv":"Savez jevrejskih opština Srbije","parent_id":"0","grupisanje":"posebni"},{"id":"15","naziv":"Komisija za zaštitu konkurencije","parent_id":"1","grupisanje":"posebni"},{"id":"150","naziv":"Nacionalni savet aškalijske nacinalne manjine","parent_id":"15000","grupisanje":"nacionalni_savet"},{"id":"151","naziv":"Nacionalni savet crnogorske nacionalne manjine","parent_id":"15000","grupisanje":"nacionalni_savet"},{"id":"16","naziv":"Komisija za ispitivanje odgovornosti za kršenje ljudskih prava","parent_id":"m","grupisanje":"posebni"},{"id":"17","naziv":"Komisija za sprovođenje programa zaštite","parent_id":"m","grupisanje":"posebni"},{"id":"18","naziv":"Komisija za hartije od vrednosti","parent_id":"1","grupisanje":"posebni"},{"id":"19","naziv":"Republička komisija za zaštitu prava u postupcima javnih nabavki","parent_id":"1","grupisanje":"posebni"},{"id":"2","naziv":"Predsednik Republike","parent_id":"0","grupisanje":"najvisi"},{"id":"20","naziv":"Socijalno-ekonomski savet RS","parent_id":"m","grupisanje":"posebni"},{"id":"21","naziv":"Narodna banka Srbije","parent_id":"2","grupisanje":"posebni"},{"id":"22","naziv":"Nacionalni savet za visoko obrazovanje","parent_id":"m","grupisanje":"nacionalni_savet"},{"id":"23","naziv":"Regulatorna agencija za elektronske komunikacije i poštanske usluge","parent_id":"3","grupisanje":"posebni"},{"id":"24","naziv":"Regulatorno telo za elektronske medije","parent_id":"m","grupisanje":"posebni"},{"id":"25","naziv":"Državno pravobranilaštvo","parent_id":"32","grupisanje":"najvisi"},{"id":"26","naziv":"Fiskalni savet","parent_id":"m","grupisanje":"posebni"},{"id":"27","naziv":"Ministarstvo spoljnih poslova","parent_id":"3","grupisanje":"ministarstvo"},{"id":"28","naziv":"Ministarstvo odbrane","parent_id":"3","grupisanje":"ministarstvo"},{"id":"29","naziv":"Ministarstvo unutrašnjih poslova","parent_id":"3","grupisanje":"ministarstvo"},{"id":"3","naziv":"Vlada Republike Srbije","parent_id":"1","grupisanje":"najvisi"},{"id":"30","naziv":"Ministarstvo finansija","parent_id":"3","grupisanje":"ministarstvo"},{"id":"31","naziv":"Ministarstvo privrede","parent_id":"3","grupisanje":"ministarstvo"},{"id":"32","naziv":"Ministarstvo pravde","parent_id":"3","grupisanje":"ministarstvo"},{"id":"33","naziv":"Ministarstvo poljoprivrede, šumarstva i vodoprivrede","parent_id":"3","grupisanje":"ministarstvo"},{"id":"34","naziv":"Ministarstvo zaštite životne sredine","parent_id":"3","grupisanje":"ministarstvo"},{"id":"3493","naziv":"Privredni apelacioni sud","parent_id":"5","grupisanje":"sud"},{"id":"3494","naziv":"Privredni sud u Beogradu","parent_id":"3493","grupisanje":"sud"},{"id":"3495","naziv":"Privredni sud u Valjevu","parent_id":"3493","grupisanje":"sud"},{"id":"3496","naziv":"Privredni sud u Zaječaru","parent_id":"3493","grupisanje":"sud"},{"id":"3497","naziv":"Privredni sud u Zrenjaninu","parent_id":"3493","grupisanje":"sud"},{"id":"3498","naziv":"Privredni sud u Kraljevu","parent_id":"3493","grupisanje":"sud"},{"id":"3499","naziv":"Privredni sud u Kragujevcu","parent_id":"3493","grupisanje":"sud"},{"id":"35","naziv":"Ministarstvo za evropske integracije","parent_id":"3","grupisanje":"ministarstvo"},{"id":"3500","naziv":"Privredni sud u Leskovcu","parent_id":"3493","grupisanje":"sud"},{"id":"3501","naziv":"Privredni sud u Nišu","parent_id":"3493","grupisanje":"sud"},{"id":"3502","naziv":"Privredni sud u Novom Sadu","parent_id":"3493","grupisanje":"sud"},{"id":"3503","naziv":"Privredni sud u Pančevu","parent_id":"3493","grupisanje":"sud"},{"id":"3504","naziv":"Privredni sud u u Požarevcu","parent_id":"3493","grupisanje":"sud"},{"id":"3505","naziv":"Privredni sud u Somboru","parent_id":"3493","grupisanje":"sud"},{"id":"3506","naziv":"Privredni sud u Sremskoj Mitrovici","parent_id":"3493","grupisanje":"sud"},{"id":"3507","naziv":"Privredni sud u Subotici","parent_id":"3493","grupisanje":"sud"},{"id":"3508","naziv":"Privredni sud u Užicu","parent_id":"3493","grupisanje":"sud"},{"id":"3509","naziv":"Privredni sud u Čačku","parent_id":"3493","grupisanje":"sud"},{"id":"3510","naziv":"Upravni sud","parent_id":"5","grupisanje":"sud"},{"id":"3511","naziv":"Apelacioni sud u Beogradu","parent_id":"5","grupisanje":"sud"},{"id":"3512","naziv":"Apelacioni sud u Kragujevcu","parent_id":"5","grupisanje":"sud"},{"id":"3513","naziv":"Apelacioni sud u Nišu","parent_id":"5","grupisanje":"sud"},{"id":"3514","naziv":"Apelacioni sud u Novom Sadu","parent_id":"5","grupisanje":"sud"},{"id":"3515","naziv":"Viši sud u Beogradu","parent_id":"3511","grupisanje":"sud"},{"id":"3516","naziv":"Viši sud u Valjevu","parent_id":"3511","grupisanje":"sud"},{"id":"3517","naziv":"Viši sud u Vranju","parent_id":"3513","grupisanje":"sud"},{"id":"3518","naziv":"Viši sud u Zaječaru","parent_id":"3513","grupisanje":"sud"},{"id":"3519","naziv":"Viši sud u Zrenjaninu","parent_id":"3514","grupisanje":"sud"},{"id":"3520","naziv":"Viši sud u Jagodini","parent_id":"3512","grupisanje":"sud"},{"id":"3521","naziv":"Viši sud u Kragujevcu","parent_id":"3512","grupisanje":"sud"},{"id":"3522","naziv":"Viši sud u Kraljevu","parent_id":"3512","grupisanje":"sud"},{"id":"3523","naziv":"Viši sud u Kruševcu","parent_id":"3512","grupisanje":"sud"},{"id":"3524","naziv":"Viši sud u Leskovcu","parent_id":"3513","grupisanje":"sud"},{"id":"3525","naziv":"Viši sud u Negotinu","parent_id":"3513","grupisanje":"sud"},{"id":"3526","naziv":"Viši sud u Nišu","parent_id":"3513","grupisanje":"sud"},{"id":"3527","naziv":"Viši sud u Novom Pazaru","parent_id":"3512","grupisanje":"sud"},{"id":"3528","naziv":"Viši sud u Novom Sadu","parent_id":"3514","grupisanje":"sud"},{"id":"3529","naziv":"Viši sud u Pančevu","parent_id":"3511","grupisanje":"sud"},{"id":"3530","naziv":"Viši sud u Pirotu","parent_id":"3513","grupisanje":"sud"},{"id":"3531","naziv":"Viši sud u Požarevcu","parent_id":"3512","grupisanje":"sud"},{"id":"3532","naziv":"Viši sud u Prokuplju","parent_id":"3513","grupisanje":"sud"},{"id":"3533","naziv":"Viši sud u Smederevu","parent_id":"3511","grupisanje":"sud"},{"id":"3534","naziv":"Viši sud u Somboru","parent_id":"3514","grupisanje":"sud"},{"id":"3535","naziv":"Viši sud u Sremskoj Mitrovici","parent_id":"3514","grupisanje":"sud"},{"id":"3536","naziv":"Viši sud u Subotici","parent_id":"3514","grupisanje":"sud"},{"id":"3537","naziv":"Viši sud u Užicu","parent_id":"3512","grupisanje":"sud"},{"id":"3538","naziv":"Viši sud u Čačku","parent_id":"3512","grupisanje":"sud"},{"id":"3539","naziv":"Viši sud u Šapcu","parent_id":"3514","grupisanje":"sud"},{"id":"3540","naziv":"Prvi osnovni sud u Beogradu","parent_id":"3515","grupisanje":"sud"},{"id":"3541","naziv":"Drugi osnovni u Beogradu","parent_id":"3515","grupisanje":"sud"},{"id":"3542","naziv":"Treći osnovni sud u Beogradu","parent_id":"3515","grupisanje":"sud"},{"id":"3543","naziv":"Osnovni sud u Aleksincu","parent_id":"3526","grupisanje":"sud"},{"id":"3544","naziv":"Osnovni sud u Aranđelovcu","parent_id":"3521","grupisanje":"sud"},{"id":"3545","naziv":"Osnovni sud u Bačkoj Palanci","parent_id":"3528","grupisanje":"sud"},{"id":"3546","naziv":"Osnovni sud u Bečeju","parent_id":"3519","grupisanje":"sud"},{"id":"3547","naziv":"Osnovni sud u Boru","parent_id":"3518","grupisanje":"sud"},{"id":"3548","naziv":"Osnovni sud u Brusu","parent_id":"3523","grupisanje":"sud"},{"id":"3549","naziv":"Osnovni sud u Bujanavcu","parent_id":"3517","grupisanje":"sud"},{"id":"3550","naziv":"Osnovni sud u Valjevu","parent_id":"3516","grupisanje":"sud"},{"id":"3551","naziv":"Osnovni sud u Velikoj Plani","parent_id":"3533","grupisanje":"sud"},{"id":"3552","naziv":"Osnovni sud u Velikom Gradištu","parent_id":"3531","grupisanje":"sud"},{"id":"3553","naziv":"Osnovni sud u Vranju","parent_id":"3517","grupisanje":"sud"},{"id":"3554","naziv":"Osnovni sud u Vrbasu","parent_id":"3534","grupisanje":"sud"},{"id":"3555","naziv":"Osnovni sud u Vršcu","parent_id":"3529","grupisanje":"sud"},{"id":"3556","naziv":"Osnovni sud u Gornjem Milanovcu","parent_id":"3538","grupisanje":"sud"},{"id":"3557","naziv":"Osnovni sud u Despotovcu","parent_id":"3520","grupisanje":"sud"},{"id":"3558","naziv":"Osnovni sud u Dimitrovgradu","parent_id":"3530","grupisanje":"sud"},{"id":"3559","naziv":"Osnovni sud u Zaječaru","parent_id":"3518","grupisanje":"sud"},{"id":"3560","naziv":"Osnovni sud u Zrenjaninu","parent_id":"3519","grupisanje":"sud"},{"id":"3561","naziv":"Osnovni sud u Ivanjici","parent_id":"3538","grupisanje":"sud"},{"id":"3562","naziv":"Osnovni sud u Jagodini","parent_id":"3520","grupisanje":"sud"},{"id":"3563","naziv":"Osnovni sud u Kikindi","parent_id":"3519","grupisanje":"sud"},{"id":"3564","naziv":"Osnovni sud u Knjaževcu","parent_id":"3518","grupisanje":"sud"},{"id":"3565","naziv":"Osnovni sud u Kragujevcu","parent_id":"3521","grupisanje":"sud"},{"id":"3566","naziv":"Osnovni sud u Kraljevu","parent_id":"3522","grupisanje":"sud"},{"id":"3567","naziv":"Osnovni sud u Kruševcu","parent_id":"3523","grupisanje":"sud"},{"id":"3568","naziv":"Osnovni sud Kuršumlija","parent_id":"3532","grupisanje":"sud"},{"id":"3569","naziv":"Osnovni sud u Lazarevcu","parent_id":"3515","grupisanje":"sud"},{"id":"3570","naziv":"Osnovni sud u Lebanu","parent_id":"3524","grupisanje":"sud"},{"id":"3571","naziv":"Osnovni sud u Leskovcu","parent_id":"3524","grupisanje":"sud"},{"id":"3572","naziv":"Osnovni sud u Loznici","parent_id":"3539","grupisanje":"sud"},{"id":"3573","naziv":"Osnovni sud u Majdanpeku","parent_id":"3525","grupisanje":"sud"},{"id":"3574","naziv":"Osnovni sud u Mionici","parent_id":"3516","grupisanje":"sud"},{"id":"3575","naziv":"Osnovni sud Mladenovac","parent_id":"3515","grupisanje":"sud"},{"id":"3576","naziv":"Osnovni sud u Negotinu","parent_id":"3525","grupisanje":"sud"},{"id":"3577","naziv":"Osnovni sud u Nišu","parent_id":"3526","grupisanje":"sud"},{"id":"3578","naziv":"Osnovni sud u Novom Pazaru","parent_id":"3527","grupisanje":"sud"},{"id":"3579","naziv":"Osnovni sud u Novom Sadu","parent_id":"3528","grupisanje":"sud"},{"id":"3580","naziv":"Osnovni sud u Obrenovcu","parent_id":"3515","grupisanje":"sud"},{"id":"3581","naziv":"Osnovni sud u Pančevu","parent_id":"3529","grupisanje":"sud"},{"id":"3582","naziv":"Osnovni sud u Paraćinu","parent_id":"3520","grupisanje":"sud"},{"id":"3583","naziv":"Osnovni sud u Petrovcu na Mlavi","parent_id":"3531","grupisanje":"sud"},{"id":"3584","naziv":"Osnovni sud u Pirotu","parent_id":"3530","grupisanje":"sud"},{"id":"3585","naziv":"Osnovni sud u Požarevcu","parent_id":"3531","grupisanje":"sud"},{"id":"3586","naziv":"Osnovni sud u Požegi","parent_id":"3537","grupisanje":"sud"},{"id":"3587","naziv":"Osnovni sud u Priboju","parent_id":"3537","grupisanje":"sud"},{"id":"3588","naziv":"Osnovni sud u Prijepolju","parent_id":"3537","grupisanje":"sud"},{"id":"3589","naziv":"Osnovni sud u Prokuplju","parent_id":"3532","grupisanje":"sud"},{"id":"3590","naziv":"Osnovni sud u Raškoj","parent_id":"3522","grupisanje":"sud"},{"id":"3591","naziv":"Osnovni sud u Rumi","parent_id":"3535","grupisanje":"sud"},{"id":"3592","naziv":"Osnovni sud u Senti","parent_id":"3536","grupisanje":"sud"},{"id":"3593","naziv":"Osnovni sud u Sjenici","parent_id":"3527","grupisanje":"sud"},{"id":"3594","naziv":"Osnovni sud u Smederevu","parent_id":"3533","grupisanje":"sud"},{"id":"3595","naziv":"Osnovni sud u Somboru","parent_id":"3534","grupisanje":"sud"},{"id":"3596","naziv":"Osnovni sud u Sremskoj Mitrovici","parent_id":"3535","grupisanje":"sud"},{"id":"3597","naziv":"Osnovni sud u Staroj Pazovi","parent_id":"3535","grupisanje":"sud"},{"id":"3598","naziv":"Osnovni sud u Subotici","parent_id":"3536","grupisanje":"sud"},{"id":"3599","naziv":"Osnovni sud u Surdulici","parent_id":"3517","grupisanje":"sud"},{"id":"36","naziv":"Ministarstvo državne uprave i lokalne samouprave","parent_id":"3","grupisanje":"ministarstvo"},{"id":"3600","naziv":"Osnovni sud u Trsteniku","parent_id":"3523","grupisanje":"sud"},{"id":"3601","naziv":"Osnovni sud u Ubu","parent_id":"3516","grupisanje":"sud"},{"id":"3602","naziv":"Osnovni sud u Užicu","parent_id":"3537","grupisanje":"sud"},{"id":"3603","naziv":"Osnovni sud u Čačku","parent_id":"3538","grupisanje":"sud"},{"id":"3604","naziv":"Osnovni sud u Šapcu","parent_id":"3539","grupisanje":"sud"},{"id":"3605","naziv":"Osnovni sud u Šidu","parent_id":"3535","grupisanje":"sud"},{"id":"3606","naziv":"Prekršajni apelacioni sud u Beogradu","parent_id":"5","grupisanje":"sud"},{"id":"3607","naziv":"Prekršajni sud u Aranđelovcu","parent_id":"3606","grupisanje":"sud"},{"id":"3608","naziv":"Prekršajni sud u Bačkoj Palanci","parent_id":"3606","grupisanje":"sud"},{"id":"3609","naziv":"Prekršajni sud u Beogradu","parent_id":"3606","grupisanje":"sud"},{"id":"3610","naziv":"Prekršajni sud u Bečeju","parent_id":"3606","grupisanje":"sud"},{"id":"3611","naziv":"Prekršajni sud u Valjevu","parent_id":"3606","grupisanje":"sud"},{"id":"3612","naziv":"Prekršajni sud u Vranju","parent_id":"3606","grupisanje":"sud"},{"id":"3613","naziv":"Prekršajni sud u Vršcu","parent_id":"3606","grupisanje":"sud"},{"id":"3614","naziv":"Prekršajni sud u Gornjem Milanovcu","parent_id":"3606","grupisanje":"sud"},{"id":"3615","naziv":"Prekršajni sud u Zaječaru","parent_id":"3606","grupisanje":"sud"},{"id":"3616","naziv":"Prekršajni sud u Zrenjaninu","parent_id":"3606","grupisanje":"sud"},{"id":"3617","naziv":"Prekršajni sud u Jagodini","parent_id":"3606","grupisanje":"sud"},{"id":"3618","naziv":"Prekršajni sud u Kikindi","parent_id":"3606","grupisanje":"sud"},{"id":"3619","naziv":"Prekršajni sud u Kragujevcu","parent_id":"3606","grupisanje":"sud"},{"id":"3620","naziv":"Prekršajni sud u Kraljevu","parent_id":"3606","grupisanje":"sud"},{"id":"3621","naziv":"Prekršajni sud u Kruševcu","parent_id":"3606","grupisanje":"sud"},{"id":"3622","naziv":"Prekršajni sud u Lazarevcu","parent_id":"3606","grupisanje":"sud"},{"id":"3623","naziv":"Prekršajni sud u Leskovac","parent_id":"3606","grupisanje":"sud"},{"id":"3624","naziv":"Prekršajni sud u Loznica","parent_id":"3606","grupisanje":"sud"},{"id":"3625","naziv":"Prekršajni sud u Mladenovcu","parent_id":"3606","grupisanje":"sud"},{"id":"3626","naziv":"Prekršajni sud u Negotinu","parent_id":"3606","grupisanje":"sud"},{"id":"3627","naziv":"Prekršajni sud u Novi Sad","parent_id":"3606","grupisanje":"sud"},{"id":"3628","naziv":"Prekršajni sud u Niš","parent_id":"3606","grupisanje":"sud"},{"id":"3629","naziv":"Prekršajni sud u Novi Pazar","parent_id":"3606","grupisanje":"sud"},{"id":"3630","naziv":"Prekršajni sud u Obrenovcu","parent_id":"3606","grupisanje":"sud"},{"id":"3631","naziv":"Prekršajni sud u Pančevu","parent_id":"3606","grupisanje":"sud"},{"id":"3632","naziv":"Prekršajni sud u Paraćinu","parent_id":"3606","grupisanje":"sud"},{"id":"3633","naziv":"Prekršajni sud u Pirotu","parent_id":"3606","grupisanje":"sud"},{"id":"3634","naziv":"Prekršajni sud u Požarevcu","parent_id":"3606","grupisanje":"sud"},{"id":"3635","naziv":"Prekršajni sud u Požegi","parent_id":"3606","grupisanje":"sud"},{"id":"3636","naziv":"Prekršajni sud u Preševu","parent_id":"3606","grupisanje":"sud"},{"id":"3637","naziv":"Prekršajni sud u Prijepolju","parent_id":"3606","grupisanje":"sud"},{"id":"3638","naziv":"Prekršajni sud u Prokuplju","parent_id":"3606","grupisanje":"sud"},{"id":"3639","naziv":"Prekršajni sud u Raški","parent_id":"3606","grupisanje":"sud"},{"id":"3640","naziv":"Prekršajni sud u Rumi","parent_id":"3606","grupisanje":"sud"},{"id":"3641","naziv":"Prekršajni sud u Senti","parent_id":"3606","grupisanje":"sud"},{"id":"3642","naziv":"Prekršajni sud u Sjenici","parent_id":"3606","grupisanje":"sud"},{"id":"3643","naziv":"Prekršajni sud u Smederevu","parent_id":"3606","grupisanje":"sud"},{"id":"3644","naziv":"Prekršajni sud u Somboru","parent_id":"3606","grupisanje":"sud"},{"id":"3645","naziv":"Prekršajni sud u Sremskoj Mitrovici","parent_id":"3606","grupisanje":"sud"},{"id":"3646","naziv":"Prekršajni sud u Subotici","parent_id":"3606","grupisanje":"sud"},{"id":"3647","naziv":"Prekršajni sud u Trsteniku","parent_id":"3606","grupisanje":"sud"},{"id":"3648","naziv":"Prekršajni sud u Užicu","parent_id":"3606","grupisanje":"sud"},{"id":"3649","naziv":"Prekršajni sud u Čačku","parent_id":"3606","grupisanje":"sud"},{"id":"3650","naziv":"Prekršajni sud u Šapcu","parent_id":"3606","grupisanje":"sud"},{"id":"3651","naziv":"Apelaciono javno tužilaštvo u Beogradu","parent_id":"6","grupisanje":"tuzilastvo"},{"id":"3652","naziv":"Apelaciono javno tužilaštvo u Kragujevcu","parent_id":"6","grupisanje":"tuzilastvo"},{"id":"3653","naziv":"Apelaciono javno tužilaštvo u Nišu","parent_id":"6","grupisanje":"tuzilastvo"},{"id":"3654","naziv":"Apelaciono javno tužilaštvo u Novom Sadu","parent_id":"6","grupisanje":"tuzilastvo"},{"id":"3655","naziv":"Više javno tužilaštvo Beograd","parent_id":"3651","grupisanje":"tuzilastvo"},{"id":"3656","naziv":"Više tužilaštvo Valjevo","parent_id":"3651","grupisanje":"tuzilastvo"},{"id":"3657","naziv":"Više tužilaštvo Vranje","parent_id":"3653","grupisanje":"tuzilastvo"},{"id":"3658","naziv":"Više tužilaštvo Zaječar","parent_id":"3653","grupisanje":"tuzilastvo"},{"id":"3659","naziv":"Više javno tužilaštvo Zrenjanin","parent_id":"3654","grupisanje":"tuzilastvo"},{"id":"3660","naziv":"Više javno tužilaštvo Jagodina","parent_id":"3652","grupisanje":"tuzilastvo"},{"id":"3661","naziv":"Više javno tužilaštvo Kragujevac","parent_id":"3652","grupisanje":"tuzilastvo"},{"id":"3662","naziv":"Više javno tužilaštvo Kraljevo","parent_id":"3652","grupisanje":"tuzilastvo"},{"id":"3663","naziv":"Više javno tužilaštvo Kruševac","parent_id":"3652","grupisanje":"tuzilastvo"},{"id":"3664","naziv":"Više javno tužilaštvo Leskovac","parent_id":"3653","grupisanje":"tuzilastvo"},{"id":"3665","naziv":"Više javno tužilaštvo Negotin","parent_id":"3653","grupisanje":"tuzilastvo"},{"id":"3666","naziv":"Više javno tužilaštvo Niš","parent_id":"3653","grupisanje":"tuzilastvo"},{"id":"3667","naziv":"Više javno tužilaštvo Novi Pazar","parent_id":"3652","grupisanje":"tuzilastvo"},{"id":"3668","naziv":"Više javno tužilaštvo Novi Sad","parent_id":"3654","grupisanje":"tuzilastvo"},{"id":"3669","naziv":"Više javno tužilaštvo Pančevo","parent_id":"3651","grupisanje":"tuzilastvo"},{"id":"3670","naziv":"Više javno tužilaštvo Pirot","parent_id":"3653","grupisanje":"tuzilastvo"},{"id":"3671","naziv":"Više javno tužilaštvo Požarevac","parent_id":"3652","grupisanje":"tuzilastvo"},{"id":"3672","naziv":"Više javno tužilaštvo Prokuplje","parent_id":"3653","grupisanje":"tuzilastvo"},{"id":"3673","naziv":"Više javno tužilaštvo Smederevo","parent_id":"3651","grupisanje":"tuzilastvo"},{"id":"3674","naziv":"Više javno tužilaštvo Sombor","parent_id":"3654","grupisanje":"tuzilastvo"},{"id":"3675","naziv":"Više javno tužilaštvo Sremska Mitrovica","parent_id":"3654","grupisanje":"tuzilastvo"},{"id":"3676","naziv":"Više javno tužilaštvo Subotica","parent_id":"3654","grupisanje":"tuzilastvo"},{"id":"3677","naziv":"Više javno tužilaštvo Užice","parent_id":"3652","grupisanje":"tuzilastvo"},{"id":"3678","naziv":"Više javno tužilaštvo Čačak","parent_id":"3652","grupisanje":"tuzilastvo"},{"id":"3679","naziv":"Više javno tužilaštvo Šabac","parent_id":"3654","grupisanje":"tuzilastvo"},{"id":"3680","naziv":"Tužilaštvo za ratne zločine u Beogradu","parent_id":"6","grupisanje":"tuzilastvo"},{"id":"3681","naziv":"Tužilaštvo za organizovani kriminal","parent_id":"6","grupisanje":"tuzilastvo"},{"id":"3682","naziv":"Prvo osnovno javno tužilaštvo u Beogradu","parent_id":"3655","grupisanje":"tuzilastvo"},{"id":"3683","naziv":"Drugo osnovno javno tužilaštvo u Beogradu","parent_id":"3655","grupisanje":"tuzilastvo"},{"id":"3684","naziv":"Treće osnovno javno tužilaštvo u Beogradu","parent_id":"3655","grupisanje":"tuzilastvo"},{"id":"3685","naziv":"Osnovno javno tužilaštvo u Aleksincu","parent_id":"3666","grupisanje":"tuzilastvo"},{"id":"3686","naziv":"Osnovno javno tužilaštvo u Aranđelovcu","parent_id":"3661","grupisanje":"tuzilastvo"},{"id":"3687","naziv":"Osnovno javno tužilaštvo u Bačkoj Palanci","parent_id":"3668","grupisanje":"tuzilastvo"},{"id":"3688","naziv":"Osnovno javno tužilaštvo u Bečeju","parent_id":"3659","grupisanje":"tuzilastvo"},{"id":"3689","naziv":"Osnovno javno tužilaštvo u Boru","parent_id":"3658","grupisanje":"tuzilastvo"},{"id":"3690","naziv":"Osnovno javno tužilaštvo u Brusu","parent_id":"3663","grupisanje":"tuzilastvo"},{"id":"3691","naziv":"Osnovno javno tužilaštvo u Valjevu","parent_id":"3656","grupisanje":"tuzilastvo"},{"id":"3692","naziv":"Osnovno javno tužilaštvo u Velikoj Plani","parent_id":"3673","grupisanje":"tuzilastvo"},{"id":"3693","naziv":"Osnovno javno tužilaštvo u Velikom Gradištu","parent_id":"3671","grupisanje":"tuzilastvo"},{"id":"3694","naziv":"Osnovno javno tužilaštvo u Vladičinom Hanu","parent_id":"3657","grupisanje":"tuzilastvo"},{"id":"3695","naziv":"Osnovno javno tužilaštvo u Vranju","parent_id":"3657","grupisanje":"tuzilastvo"},{"id":"3696","naziv":"Osnovno javno tužilaštvo u Vrbasu","parent_id":"3674","grupisanje":"tuzilastvo"},{"id":"3697","naziv":"Osnovno javno tužilaštvo u Vršcu","parent_id":"3669","grupisanje":"tuzilastvo"},{"id":"3698","naziv":"Osnovno javno tužilaštvo u Gornjem Milanovcu","parent_id":"3678","grupisanje":"tuzilastvo"},{"id":"3699","naziv":"Osnovno javno tužilaštvo u Despotovcu","parent_id":"3660","grupisanje":"tuzilastvo"},{"id":"37","naziv":"Ministarstvo građevinarstva, saobraćaja i infrastrukture","parent_id":"3","grupisanje":"ministarstvo"},{"id":"3700","naziv":"Osnovno javno tužilaštvo u Zaječaru","parent_id":"3658","grupisanje":"tuzilastvo"},{"id":"3701","naziv":"Osnovno javno tužilaštvo u Zrenjaninu","parent_id":"3659","grupisanje":"tuzilastvo"},{"id":"3702","naziv":"Osnovno javno tužilaštvo u Jagodini","parent_id":"3660","grupisanje":"tuzilastvo"},{"id":"3703","naziv":"Osnovno javno tužilaštvo u Kikindi","parent_id":"3659","grupisanje":"tuzilastvo"},{"id":"3704","naziv":"Osnovno javno tužilaštvo u Kragujevcu","parent_id":"3661","grupisanje":"tuzilastvo"},{"id":"3705","naziv":"Osnovno javno tužilaštvo u Kraljevu","parent_id":"3662","grupisanje":"tuzilastvo"},{"id":"3706","naziv":"Osnovno javno tužilaštvo u Kruševcu","parent_id":"3663","grupisanje":"tuzilastvo"},{"id":"3707","naziv":"Osnovno javno tužilaštvo u Kuršumliji","parent_id":"3672","grupisanje":"tuzilastvo"},{"id":"3708","naziv":"Osnovno javno tužilaštvo u Lazarevcu","parent_id":"3655","grupisanje":"tuzilastvo"},{"id":"3709","naziv":"Osnovno javno tužilaštvo u Leskovcu","parent_id":"3664","grupisanje":"tuzilastvo"},{"id":"3710","naziv":"Osnovno javno tužilaštvo u Lebanu","parent_id":"3664","grupisanje":"tuzilastvo"},{"id":"3711","naziv":"Osnovno javno tužilaštvo u Loznici","parent_id":"3679","grupisanje":"tuzilastvo"},{"id":"3712","naziv":"Osnovno javno tužilaštvo u Mionici","parent_id":"3656","grupisanje":"tuzilastvo"},{"id":"3713","naziv":"Osnovno javno tužilaštvo u Mladenovcu","parent_id":"3655","grupisanje":"tuzilastvo"},{"id":"3714","naziv":"Osnovno javno tužilaštvo u Negotinu","parent_id":"3665","grupisanje":"tuzilastvo"},{"id":"3715","naziv":"Osnovno javno tužilaštvo u Nišu","parent_id":"3666","grupisanje":"tuzilastvo"},{"id":"3716","naziv":"Osnovno javno tužilaštvo u Novom Pazaru","parent_id":"3667","grupisanje":"tuzilastvo"},{"id":"3717","naziv":"Osnovno javno tužilaštvo u Novom Sadu","parent_id":"3668","grupisanje":"tuzilastvo"},{"id":"3718","naziv":"Osnovno javno tužilaštvo u Obrenovcu","parent_id":"3655","grupisanje":"tuzilastvo"},{"id":"3719","naziv":"Osnovno javno tužilaštvo u Pančevu","parent_id":"3669","grupisanje":"tuzilastvo"},{"id":"3720","naziv":"Osnovno javno tužilaštvo u Paraćinu","parent_id":"3660","grupisanje":"tuzilastvo"},{"id":"3721","naziv":"Osnovno javno tužilaštvo u Petrovcu na Mlavi","parent_id":"3671","grupisanje":"tuzilastvo"},{"id":"3722","naziv":"Osnovno javno tužilaštvo u Pirotu","parent_id":"3670","grupisanje":"tuzilastvo"},{"id":"3723","naziv":"Osnovno javno tužilaštvo u Požarevcu","parent_id":"3671","grupisanje":"tuzilastvo"},{"id":"3724","naziv":"Osnovno javno tužilaštvo u Požegi","parent_id":"3677","grupisanje":"tuzilastvo"},{"id":"3725","naziv":"Osnovno javno tužilaštvo u Prijepolju","parent_id":"3677","grupisanje":"tuzilastvo"},{"id":"3726","naziv":"Osnovno javno tužilaštvo u Prokuplju","parent_id":"3672","grupisanje":"tuzilastvo"},{"id":"3727","naziv":"Osnovno javno tužilaštvo u Raškoj","parent_id":"3662","grupisanje":"tuzilastvo"},{"id":"3728","naziv":"Osnovno javno tužilaštvo u Rumi","parent_id":"3675","grupisanje":"tuzilastvo"},{"id":"3729","naziv":"Osnovno javno tužilaštvo u Senti","parent_id":"3676","grupisanje":"tuzilastvo"},{"id":"3730","naziv":"Osnovno javno tužilaštvo u Smederevu","parent_id":"3673","grupisanje":"tuzilastvo"},{"id":"3731","naziv":"Osnovno javno tužilaštvo u Somboru","parent_id":"3674","grupisanje":"tuzilastvo"},{"id":"3732","naziv":"Osnovno javno tužilaštvo u Sremskoj Mitrovici","parent_id":"3675","grupisanje":"tuzilastvo"},{"id":"3733","naziv":"Osnovno javno tužilaštvo u Staroj Pazovi","parent_id":"3675","grupisanje":"tuzilastvo"},{"id":"3734","naziv":"Osnovno javno tužilaštvo u Subotici","parent_id":"3676","grupisanje":"tuzilastvo"},{"id":"3735","naziv":"Osnovno javno tužilaštvo u Tresteniku","parent_id":"3663","grupisanje":"tuzilastvo"},{"id":"3736","naziv":"Osnovno javno tužilaštvo u Ubu","parent_id":"3656","grupisanje":"tuzilastvo"},{"id":"3737","naziv":"Osnovno javno tužilaštvo u Užicu","parent_id":"3677","grupisanje":"tuzilastvo"},{"id":"3738","naziv":"Osnovno javno tužilaštvo u Čačku","parent_id":"3678","grupisanje":"tuzilastvo"},{"id":"3739","naziv":"Osnovno javno tužilaštvo u Šapcu","parent_id":"3679","grupisanje":"tuzilastvo"},{"id":"3740","naziv":"Pokrajinska vlada","parent_id":"3741","grupisanje":"pokrajinski"},{"id":"3741","naziv":"Skupština AP Vojvodine","parent_id":"0","grupisanje":"najvisi"},{"id":"3742","naziv":"Pokrajinski sekretarijat za kulturu, javno informisanje I odnose s verskim zajednicama ","parent_id":"3740","grupisanje":"pokrajinski"},{"id":"3743","naziv":"Pokrajinski sekretarijat za obrazovanje, propise, upravu i nacionalne manjine- nacionalne zajednice","parent_id":"3740","grupisanje":"pokrajinski"},{"id":"3744","naziv":"Pokrajinski sekretarijat za finansije","parent_id":"3740","grupisanje":"pokrajinski"},{"id":"3745","naziv":"Pokrajinski sekretarijat za privredu i turizam","parent_id":"3740","grupisanje":"pokrajinski"},{"id":"3746","naziv":"Pokrajinski sekretarijat za zdravstvo","parent_id":"3740","grupisanje":"pokrajinski"},{"id":"3747","naziv":"Pokrajinski sekretarijat za regionalni razvoj, međuregionalnu saradnju i lokalnu samoupravu","parent_id":"3740","grupisanje":"pokrajinski"},{"id":"3748","naziv":"Pokrajinski sekretarijat za urbanizam i zaštitu životne sredine","parent_id":"3740","grupisanje":"pokrajinski"},{"id":"3749","naziv":"Pokrajinski sekretarijat za energetiku, građevinarstvo i saobraćaj","parent_id":"3740","grupisanje":"pokrajinski"},{"id":"3750","naziv":"Pokrajinski sekretarijat za sport i omladinu","parent_id":"3740","grupisanje":"pokrajinski"},{"id":"3751","naziv":"Pokrajinski sekretarijat za socijalnu politiku, demografiju i ravnopravnost polova","parent_id":"3740","grupisanje":"pokrajinski"},{"id":"3752","naziv":"Pokrajinski sekretarijat za visoko obrazovanje i naučnoistraživačku delatnost","parent_id":"3740","grupisanje":"pokrajinski"},{"id":"3753","naziv":"Pokrajinski sekretarijat za poljoprivredu, vodoprivredu i šumarstvo","parent_id":"3740","grupisanje":"pokrajinski"},{"id":"3754","naziv":"Uprava za zajedničke poslove pokrajinskih organa","parent_id":"3740","grupisanje":"pokrajinski"},{"id":"3755","naziv":"Stručna služba za realizaciju programa privrednog razvoja AP Vojvodine","parent_id":"3740","grupisanje":"pokrajinski"},{"id":"3756","naziv":"Pokrajinski ombudsman","parent_id":"3741","grupisanje":"pokrajinski"},{"id":"3757","naziv":"Pokrajinsko javno pravobranilaštvo Vojvodine","parent_id":"3741","grupisanje":"pokrajinski"},{"id":"3758","naziv":"Direkcija za robne rezerve AP Vojvodine","parent_id":"3740","grupisanje":"pokrajinski"},{"id":"3759","naziv":"Garancijski fond APV Vojvodine","parent_id":"m","grupisanje":"pokrajinski"},{"id":"3760","naziv":"Pokrajinski zavod za ravnopravnost polova","parent_id":"3741","grupisanje":"pokrajinski"},{"id":"3761","naziv":"Pokrajinski fond za razvoj poljoprivrede","parent_id":"3740","grupisanje":"pokrajinski"},{"id":"3762","naziv":"Pokrajinski zavod za zaštitu prirode","parent_id":"3740","grupisanje":"pokrajinski"},{"id":"3763","naziv":"Pokrajinski zavod za socijalnu zaštitu","parent_id":"3740","grupisanje":"pokrajinski"},{"id":"3764","naziv":"Razvojni fond AP Vojvodine","parent_id":"m","grupisanje":"pokrajinski"},{"id":"3765","naziv":"Fond za podršku investicija u Vojvodini \"Vojvodina investment promotion – VIP\"","parent_id":"m","grupisanje":"pokrajinski"},{"id":"3766","naziv":"Fond za pružanje pomoći izbeglim, prognanim i raseljenim licima","parent_id":"3740","grupisanje":"pokrajinski"},{"id":"3767","naziv":"Uprava za kapitalna ulaganja","parent_id":"3740","grupisanje":"pokrajinski"},{"id":"3768","naziv":"Fond “Evropski poslovi”","parent_id":"3740","grupisanje":"pokrajinski"},{"id":"3769","naziv":"Kancelarija za inkluziju Roma","parent_id":"3740","grupisanje":"pokrajinski"},{"id":"3770","naziv":"Javno preduzeće \"Forum – izdavačka delatnost, Novi Sad\"","parent_id":"0","grupisanje":"pokrajinski"},{"id":"3771","naziv":"Javno preduzeće \"Vojvodinašume\"","parent_id":"0","grupisanje":"pokrajinski"},{"id":"3772","naziv":"JVP \"Vode Vojvodine\"","parent_id":"0","grupisanje":"pokrajinski"},{"id":"3773","naziv":"Javno preduzeće za prostorno i urbanističko planiranje i projektiranje \"Zavod za urbanizam Vojvodine\"","parent_id":"0","grupisanje":"pokrajinski"},{"id":"3774","naziv":"RTV Vojvodina","parent_id":"0","grupisanje":"pokrajinski"},{"id":"3775","naziv":"Pokrajinski zavod za sport i medicinu sporta","parent_id":"3740","grupisanje":"pokrajinski"},{"id":"3776","naziv":"Pokrajinski zavod za zaštitu spomenika kulture","parent_id":"3740","grupisanje":"pokrajinski"},{"id":"3777","naziv":"Pokrajinski zavod za zaštitu prirode","parent_id":"0","grupisanje":"pokrajinski"},{"id":"3778","naziv":"\"Pedagoški zavod Vojvodine\"","parent_id":"3741","grupisanje":"pokrajinski"},{"id":"3779","naziv":"\"Zavod za kulturu Vojvodine\"","parent_id":"0","grupisanje":"pokrajinski"},{"id":"3780","naziv":"Arhiv Vojvodine","parent_id":"3740","grupisanje":"pokrajinski"},{"id":"3781","naziv":"\"Muzej Vojvodine Novi Sad\"","parent_id":"3740","grupisanje":"pokrajinski"},{"id":"3782","naziv":"Muzej savremene umetnosti Vojvodine u Novom Sadu","parent_id":"0","grupisanje":"pokrajinski"},{"id":"3783","naziv":"Pozorišni muzej Vojvodine","parent_id":"0","grupisanje":"pokrajinski"},{"id":"3784","naziv":"Srpsko narodno pozorište","parent_id":"3740","grupisanje":"pokrajinski"},{"id":"3785","naziv":"Novinsko-izdavačka Ustanova \" Nova misao\"","parent_id":"0","grupisanje":"pokrajinski"},{"id":"3786","naziv":"Galerija likovne umetnosti poklon zbirka Rajka Mamuzića","parent_id":"3740","grupisanje":"pokrajinski"},{"id":"3787","naziv":"Spomen-zbirka Pavla Beljanskog","parent_id":"3740","grupisanje":"pokrajinski"},{"id":"3788","naziv":"Privredna komora Vojvodine","parent_id":"0","grupisanje":"pokrajinski"},{"id":"3789","naziv":"Vojvođanska akademija nauka i umetnosti","parent_id":"3740","grupisanje":"pokrajinski"},{"id":"3790","naziv":"Turistička organizacija Vojvodine","parent_id":"3740","grupisanje":""},{"id":"3791","naziv":"Lekarska komora Vojvodine","parent_id":"0","grupisanje":"pokrajinski"},{"id":"3792","naziv":"Sportski savez Vojvodine","parent_id":"0","grupisanje":"pokrajinski"},{"id":"38","naziv":"Ministarstvo rudarstva i energetike","parent_id":"3","grupisanje":"ministarstvo"},{"id":"39","naziv":"Ministarstvo trgovine, turizma i telekomunikacija","parent_id":"3","grupisanje":"ministarstvo"},{"id":"4","naziv":"Ustavni sud Srbije","parent_id":"m","grupisanje":"najvisi"},{"id":"40","naziv":"Ministarstvo prosvete, nauke i tehnološkog razvoja","parent_id":"3","grupisanje":"ministarstvo"},{"id":"41","naziv":"Ministarstvo omladine i sporta","parent_id":"3","grupisanje":"ministarstvo"},{"id":"42","naziv":"Ministarstvo zdravlja","parent_id":"3","grupisanje":"ministarstvo"},{"id":"43","naziv":"Ministarstvo za rad, zapošljavanje, boračka i socijalna pitanja","parent_id":"3","grupisanje":"ministarstvo"},{"id":"44","naziv":"Ministarstvo kulture i informisanja","parent_id":"3","grupisanje":"ministarstvo"},{"id":"45","naziv":"Agencija za zaštitu životne sredine (organ sa svojstvom pravnog lica u sastavu Ministarstva poljoprivrede, šumarstva i vodoprivrede)","parent_id":"33","grupisanje":"posebni"},{"id":"46","naziv":"Bezbednosno informativna agencija","parent_id":"3","grupisanje":"posebni"},{"id":"47","naziv":"Vojnobezbednosna agencija (organ uprave u sastavu Ministarstva odbrane)","parent_id":"28","grupisanje":"posebni"},{"id":"48","naziv":"Vojnoobaveštajna agencija (organ uprave u sastavu Ministarstva odbrane)","parent_id":"28","grupisanje":"posebni"},{"id":"49","naziv":"Republička agencija za mirno rešavanje radnih sporova","parent_id":"3","grupisanje":"posebni"},{"id":"5","naziv":"Vrhovni kasacioni sud Srbije","parent_id":"m","grupisanje":"najvisi"},{"id":"50","naziv":"Agencija za energetiku","parent_id":"1","grupisanje":"posebni"},{"id":"51","naziv":"Agencija za vođenje sporova u postupku privatizacije","parent_id":"31","grupisanje":"posebni"},{"id":"52","naziv":"Agencija za privredne registre","parent_id":"31","grupisanje":"posebni"},{"id":"53","naziv":"Agencija za licenciranje stečajnih upravnika","parent_id":"31","grupisanje":"posebni"},{"id":"54","naziv":"Agencija za lekove i medicinska sredstva","parent_id":"3","grupisanje":"posebni"},{"id":"55","naziv":"Agencija za osiguranje depozita","parent_id":"m","grupisanje":"posebni"},{"id":"56","naziv":"Republička agencija za prostorno planiranje","parent_id":"37","grupisanje":"posebni"},{"id":"57","naziv":"Agencija za osiguranje i finansiranje izvoza","parent_id":"3","grupisanje":"posebni"},{"id":"58","naziv":"Antidoping agencija Republike Srbije","parent_id":"m","grupisanje":"posebni"},{"id":"59","naziv":"Agencija za akreditaciju zdravstvenih ustanova Srbije","parent_id":"m","grupisanje":"posebni"},{"id":"6","naziv":"Republičko javno tužilaštvo","parent_id":"3","grupisanje":"najvisi"},{"id":"60","naziv":"Agencija za bezbednost saobraćaja","parent_id":"37","grupisanje":"posebni"},{"id":"61","naziv":"Republička agencija za stanovanje","parent_id":"3","grupisanje":"posebni"},{"id":"62","naziv":"Agencija za upravljanje lukama","parent_id":"37","grupisanje":"posebni"},{"id":"63","naziv":"Agencija za restituciju","parent_id":"3","grupisanje":"posebni"},{"id":"64","naziv":"Agencija za zaštitu od jonizujućih zračenja i nuklearnu sigurnost Srbije","parent_id":"3","grupisanje":"posebni"},{"id":"65","naziv":"Razvojna agencija Srbije","parent_id":"31","grupisanje":"posebni"},{"id":"66","naziv":"Republički zavod za statistiku","parent_id":"3","grupisanje":"posebni"},{"id":"67","naziv":"Republički hidrometeorološki zavod","parent_id":"3","grupisanje":"posebni"},{"id":"68","naziv":"Republički geodetski zavod","parent_id":"3","grupisanje":"posebni"},{"id":"69","naziv":"Republički seizmološki zavod","parent_id":"3","grupisanje":"posebni"},{"id":"7","naziv":"Zaštitnik građana","parent_id":"1","grupisanje":"nezavisni"},{"id":"70","naziv":"Zavod za intelektualnu svojinu","parent_id":"3","grupisanje":"posebni"},{"id":"71","naziv":"Zavod za socijalno osiguranje","parent_id":"3","grupisanje":"posebni"},{"id":"72","naziv":"Zavod za sport i medicinu sporta Republike Srbije","parent_id":"41","grupisanje":"posebni"},{"id":"73","naziv":"Zavod za unapređenje obrazovanja i vaspitanja","parent_id":"3","grupisanje":"posebni"},{"id":"74","naziv":"Zavod za vrednovanje kvaliteta obrazovanja i vaspitanja","parent_id":"3","grupisanje":"posebni"},{"id":"75","naziv":"Republički zavod za socijalnu zaštitu","parent_id":"3","grupisanje":"posebni"},{"id":"76","naziv":"Geološki zavod Srbije","parent_id":"3","grupisanje":"posebni"},{"id":"77","naziv":"Republička direkcija za imovinu Republike Srbije","parent_id":"3","grupisanje":"posebni"},{"id":"78","naziv":"Republička direkcija za vode (organ uprave u sastavu Ministarstva poljoprivrede, šumarstva i vodoprivrede)","parent_id":"33","grupisanje":"posebni"},{"id":"79","naziv":"Republička direkcija za robne rezerve","parent_id":"3","grupisanje":"posebni"},{"id":"8","naziv":"Državna revizorska institucija","parent_id":"1","grupisanje":"nezavisni"},{"id":"80","naziv":"Direkcija za mere i dragocene metale (organ uprave u sastavu Ministarstva privrede)","parent_id":"31","grupisanje":"posebni"},{"id":"81","naziv":"Direkcija za vodne puteve „Plovput“","parent_id":"37","grupisanje":"posebni"},{"id":"82","naziv":"Direkcija za železnice","parent_id":"37","grupisanje":"posebni"},{"id":"83","naziv":"Građevinska direkcija Srbije","parent_id":"37","grupisanje":"posebni"},{"id":"84","naziv":"Direkcija za građevinski inženjering","parent_id":"37","grupisanje":"posebni"},{"id":"85","naziv":"Direkcija za izgradnju i razvoj Kolubarskog okruga pogođenog zemljotresom","parent_id":"37","grupisanje":"posebni"},{"id":"86","naziv":"Direkcija za upravljanje oduzetom imovinom (organ uprave sa statusom pravnog lica u sastavu Ministarstva pravde)","parent_id":"32","grupisanje":"posebni"},{"id":"87","naziv":"Direkcija za nacionalne referentne laboratorije (organ u sastavu Ministarstva poljoprivrede, šumarstva i vodoprivrede)","parent_id":"33","grupisanje":"posebni"},{"id":"88","naziv":"Direkcija za elektronsku upravu (organ u sastavu Ministarstva za državnu upravu i lokalnu samoupravu)","parent_id":"36","grupisanje":"posebni"},{"id":"89","naziv":"Komisija za kontrolu državne pomoći","parent_id":"30","grupisanje":"posebni"},{"id":"9","naziv":"Republička izborna komisija","parent_id":"1","grupisanje":"nezavisni"},{"id":"90","naziv":"Republički fond za penzijsko i invalidsko osiguranje","parent_id":"43","grupisanje":"posebni"},{"id":"91","naziv":"Republički fond za zdravstveno osiguranje","parent_id":"42","grupisanje":"posebni"},{"id":"92","naziv":"Fond za socijalno osiguranje vojnih osiguranika","parent_id":"28","grupisanje":"posebni"},{"id":"93","naziv":"Fond za razvoj Republike Srbije","parent_id":"30","grupisanje":"posebni"},{"id":"94","naziv":"Akcionarski fond","parent_id":"31","grupisanje":"posebni"},{"id":"95","naziv":"Fond za mlade talente Dositeja","parent_id":"41","grupisanje":"posebni"},{"id":"96","naziv":"Fond solidarnosti","parent_id":"m","grupisanje":"posebni"},{"id":"97","naziv":"Kompenzacioni fond RS","parent_id":"3","grupisanje":"posebni"},{"id":"98","naziv":"Fondacija za rešavanje stambenih potreba mladih naučnih radnika Univerziteta u Beogradu","parent_id":"m","grupisanje":"posebni"},{"id":"99","naziv":"Uprava za javne nabavke","parent_id":"3","grupisanje":"posebni"},{"id":"15001","naziv":"Skupstina Opstina","parent_id":"0","grupisanje":"lokal"}] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment