/** * @private */Ext.define('Ext.d3.Helpers', { singleton: true, makeScale: function (config) { //<debug> if (!config.type) { Ext.raise('The type of scale is not specified.'); } //</debug> if (config.type === 'time') { // Time scale lives in the `time` namespace, not `scale` as with other scales. return this.make('time.scale', config); } return this.make('scale', config); }, isOrdinalScale: function (scale) { // There are no properties on D3 scales that tell the scale's type, // so we have to check if the scale has certain method(s). return typeof scale.rangePoints === 'function'; }, make: function (what, config) { // At the time of this writing, only a single D3 entity has the `type` method // (d3.svg.symbol). In this case one can use the `$type` key instead of `type` // to specify the type of entity to be made. var parts = what.split('.'), type = parts.length < 2 && (config.$type || config.type), thing; // fetch if (type) { thing = d3[what][type]; } else { thing = d3; while (parts.length) { thing = thing[parts.shift()]; } } thing = thing(); // create thing = this.configure(thing, config, type); return thing; }, /** * See https://bugzilla.mozilla.org/show_bug.cgi?id=612118 */ isBBoxable: function (selection) { if (Ext.isFirefox) { if (selection) { if (selection instanceof Element) { selection = d3.select(selection); } return document.contains(selection.node()) && selection.style('display') !== 'none' && selection.attr('visibility') !== 'hidden'; } } return true; }, getBBox: function (selection) { var display = selection.style('display'); if (display !== 'none') { return selection.node().getBBox(); } }, /** * @param {Function} thing * @param {Object} config * @param {String/Object} skip The properties in `config` that should be skipped. * @return {Function} Configured `thing`. */ configure: function (thing, config, skip) { // Examples: // axis.ticks(20).orient('top') <-> configure(axis, {ticks: 20, orient: 'top'}) // axis.ticks(d3.time.days) <-> configure(axis, {ticks: 'd3.time.days'}) // scale.domain([0, 20]) <-> configure(scale, {domain: [0, 20]}) // axis.ticks(d3.time.minutes, 15) <-> configure(axis, {$ticks: ['d3.time.minutes', 15]}) var key, apply, param; for (key in config) { apply = key.charAt(0) === '$'; if (apply) { key = key.substr(1); } if ( ( !skip || skip && (key !== skip || !(key in skip)) ) && thing[key] ) { if (apply) { param = config['$' + key].map(function (param) { if (typeof param === 'string' && !param.search('d3.')) { param = (new Function('return ' + param))(); } return param; }); thing[key].apply(thing, param); apply = false; } else { param = config[key]; if (typeof param === 'string' && !param.search('d3.')) { param = (new Function('return ' + param))(); } thing[key](param); } } } return thing; }, setDominantBaseline: function (element, baseline) { element.setAttribute('dominant-baseline', baseline); this.fakeDominantBaseline(element, baseline); if (Ext.isSafari && baseline === 'text-after-edge') { // dominant-baseline: text-after-edge doesn't work properly in Safari element.setAttribute('baseline-shift', 'super'); } }, noDominantBaseline: function () { // 'dominant-baseline' and 'alignment-baseline' don't work in IE and Edge. return Ext.isIE || Ext.isEdge; }, fakeDominantBaselineMap: { 'alphabetic': '0em', 'ideographic': '-.24em', 'hanging': '.72em', 'mathematical': '.46em', 'middle': '.22em', 'central': '.33em', 'text-after-edge': '-.26em', 'text-before-edge': '.91em' }, fakeDominantBaseline: function (element, baseline, force) { if (force || this.noDominantBaseline()) { var dy = this.fakeDominantBaselineMap[baseline]; dy && element.setAttribute('dy', dy); } }, fakeDominantBaselines: function (config) { var map = this.fakeDominantBaselineMap, selector, baseline, dy, nodeList, i, ln; // `config` is a map of the {selector: baseline} format. // Alternatively, the method takes two arguments: selector and baseline. if (this.noDominantBaseline()) { if (arguments.length > 1) { selector = arguments[0]; baseline = arguments[1]; nodeList = document.querySelectorAll(selector); dy = map[baseline]; if (dy) { for (i = 0, ln = nodeList.length; i < ln; i++) { nodeList[i].setAttribute('dy', dy); } } } else { for (selector in config) { baseline = config[selector]; dy = map[baseline]; if (dy) { nodeList = document.querySelectorAll(selector); for (i = 0, ln = nodeList.length; i < ln; i++) { nodeList[i].setAttribute('dy', dy); } } } } } }, unitMath: function (string, operation, number) { var value = parseFloat(string), unit = string.substr(value.toString().length); switch (operation) { case '*': value *= number; break; case '+': value += number; break; case '/': value /= number; break; case '-': value -= number; break; } return value.toString() + unit; }, getLinkId: function (link) { var pos = link.search('#'), // e.g. url(#path) id = link.substr(pos, link.length - pos - 1); return id; }, alignRect: function (x, y, inner, outer, selection) { var tx, ty, translation; if (outer && inner) { switch (x) { case 'center': tx = outer.width / 2 - (inner.x + inner.width / 2); break; case 'left': tx = -inner.x; break; case 'right': tx = outer.width - (inner.x + inner.width); break; default: Ext.raise('Invalid value. Valid `x` values are: center, left, right.'); } switch (y) { case 'center': ty = outer.height / 2 - (inner.y + inner.height / 2); break; case 'top': ty = -inner.y; break; case 'bottom': ty = outer.height - (inner.y + inner.height); break; default: Ext.raise('Invalid value. Valid `y` values are: center, top, bottom.'); } } if (Ext.isNumber(tx) && Ext.isNumber(ty)) { tx += outer.x; ty += outer.y; translation = [tx, ty]; selection.attr('transform', 'translate(' + translation + ')'); } } });