summaryrefslogtreecommitdiff
path: root/priv/static/js/metricsgraphics/misc/utility.js
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--priv/static/js/metricsgraphics/misc/utility.js619
1 files changed, 619 insertions, 0 deletions
diff --git a/priv/static/js/metricsgraphics/misc/utility.js b/priv/static/js/metricsgraphics/misc/utility.js
new file mode 100644
index 0000000..f68327b
--- /dev/null
+++ b/priv/static/js/metricsgraphics/misc/utility.js
@@ -0,0 +1,619 @@
+//a set of helper functions, some that we've written, others that we've borrowed
+
+MG.convert = {};
+
+MG.convert.date = function(data, accessor, time_format) {
+ time_format = (typeof time_format === "undefined") ? '%Y-%m-%d' : time_format;
+ var parse_time = d3.timeParse(time_format);
+ data = data.map(function(d) {
+ d[accessor] = parse_time(d[accessor].trim());
+ return d;
+ });
+
+ return data;
+};
+
+MG.convert.number = function(data, accessor) {
+ data = data.map(function(d) {
+ d[accessor] = Number(d[accessor]);
+ return d;
+ });
+
+ return data;
+};
+
+MG.time_format = function(utc, specifier) {
+ return utc ? d3.utcFormat(specifier) : d3.timeFormat(specifier);
+};
+
+function mg_jquery_exists() {
+ if (typeof jQuery !== 'undefined' || typeof $ !== 'undefined') {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+function mg_get_rollover_time_format(args) {
+ // if a rollover time format is defined, use that
+ if (args.rollover_time_format) {
+ return MG.time_format(args.utc_time, args.rollover_time_format);
+ }
+
+ switch (args.processed.x_time_frame) {
+ case 'millis':
+ return MG.time_format(args.utc_time, '%b %e, %Y %H:%M:%S.%L');
+ case 'seconds':
+ return MG.time_format(args.utc_time, '%b %e, %Y %H:%M:%S');
+ case 'less-than-a-day':
+ return MG.time_format(args.utc_time, '%b %e, %Y %I:%M%p');
+ case 'four-days':
+ return MG.time_format(args.utc_time, '%b %e, %Y %I:%M%p');
+ }
+
+ // default
+ return MG.time_format(args.utc_time, '%b %e, %Y');
+}
+
+function mg_data_in_plot_bounds(datum, args) {
+ return datum[args.x_accessor] >= args.processed.min_x &&
+ datum[args.x_accessor] <= args.processed.max_x &&
+ datum[args.y_accessor] >= args.processed.min_y &&
+ datum[args.y_accessor] <= args.processed.max_y;
+}
+
+function is_array(thing) {
+ return Object.prototype.toString.call(thing) === '[object Array]';
+}
+
+function is_function(thing) {
+ return Object.prototype.toString.call(thing) === '[object Function]';
+}
+
+function is_empty_array(thing) {
+ return is_array(thing) && thing.length === 0;
+}
+
+function is_object(thing) {
+ return Object.prototype.toString.call(thing) === '[object Object]';
+}
+
+function is_array_of_arrays(data) {
+ var all_elements = data.map(function(d) {
+ return is_array(d) === true && d.length > 0;
+ });
+
+ return d3.sum(all_elements) === data.length;
+}
+
+function is_array_of_objects(data) {
+ // is every element of data an object?
+ var all_elements = data.map(function(d) {
+ return is_object(d) === true;
+ });
+
+ return d3.sum(all_elements) === data.length;
+}
+
+function is_array_of_objects_or_empty(data) {
+ return is_empty_array(data) || is_array_of_objects(data);
+}
+
+function pluck(arr, accessor) {
+ return arr.map(function(d) {
+ return d[accessor]; });
+}
+
+function count_array_elements(arr) {
+ return arr.reduce(function(a, b) { a[b] = a[b] + 1 || 1;
+ return a; }, {});
+}
+
+function mg_get_bottom(args) {
+ return args.height - args.bottom;
+}
+
+function mg_get_plot_bottom(args) {
+ // returns the pixel location of the bottom side of the plot area.
+ return mg_get_bottom(args) - args.buffer;
+}
+
+function mg_get_top(args) {
+ return args.top;
+}
+
+function mg_get_plot_top(args) {
+ // returns the pixel location of the top side of the plot area.
+ return mg_get_top(args) + args.buffer;
+}
+
+function mg_get_left(args) {
+ return args.left;
+}
+
+function mg_get_plot_left(args) {
+ // returns the pixel location of the left side of the plot area.
+ return mg_get_left(args) + args.buffer;
+}
+
+function mg_get_right(args) {
+ return args.width - args.right;
+}
+
+function mg_get_plot_right(args) {
+ // returns the pixel location of the right side of the plot area.
+ return mg_get_right(args) - args.buffer;
+}
+
+//////// adding elements, removing elements /////////////
+
+function mg_exit_and_remove(elem) {
+ elem.exit().remove();
+}
+
+function mg_selectAll_and_remove(svg, cl) {
+ svg.selectAll(cl).remove();
+}
+
+function mg_add_g(svg, cl) {
+ return svg.append('g').classed(cl, true);
+}
+
+function mg_remove_element(svg, elem) {
+ svg.select(elem).remove();
+}
+
+//////// axis helper functions ////////////
+
+function mg_make_rug(args, rug_class) {
+ var svg = mg_get_svg_child_of(args.target);
+ var all_data = mg_flatten_array(args.data);
+ var rug = svg.selectAll('line.' + rug_class).data(all_data);
+
+ rug.enter()
+ .append('line')
+ .attr('class', rug_class)
+ .attr('opacity', 0.3);
+
+ //remove rug elements that are no longer in use
+ mg_exit_and_remove(rug);
+
+ //set coordinates of new rug elements
+ mg_exit_and_remove(rug);
+ return rug;
+}
+
+function mg_add_color_accessor_to_rug(rug, args, rug_mono_class) {
+ if (args.color_accessor) {
+ rug.attr('stroke', args.scalefns.colorf);
+ rug.classed(rug_mono_class, false);
+ } else {
+ rug.attr('stroke', null);
+ rug.classed(rug_mono_class, true);
+ }
+}
+
+function mg_rotate_labels(labels, rotation_degree) {
+ if (rotation_degree) {
+ labels.attr({
+ dy: 0,
+ transform: function() {
+ var elem = d3.select(this);
+ return 'rotate(' + rotation_degree + ' ' + elem.attr('x') + ',' + elem.attr('y') + ')';
+ }
+ });
+ }
+}
+
+//////////////////////////////////////////////////
+
+function mg_elements_are_overlapping(labels) {
+ labels = labels.node();
+ if (!labels) {
+ return false;
+ }
+
+ for (var i = 0; i < labels.length; i++) {
+ if (mg_is_horizontally_overlapping(labels[i], labels)) return true;
+ }
+
+ return false;
+}
+
+function mg_prevent_horizontal_overlap(labels, args) {
+ if (!labels || labels.length == 1) {
+ return;
+ }
+
+ //see if each of our labels overlaps any of the other labels
+ for (var i = 0; i < labels.length; i++) {
+ //if so, nudge it up a bit, if the label it intersects hasn't already been nudged
+ if (mg_is_horizontally_overlapping(labels[i], labels)) {
+ var node = d3.select(labels[i]);
+ var newY = +node.attr('y');
+ if (newY + 8 >= args.top) {
+ newY = args.top - 16;
+ }
+ node.attr('y', newY);
+ }
+ }
+}
+
+function mg_prevent_vertical_overlap(labels, args) {
+ if (!labels || labels.length == 1) {
+ return;
+ }
+
+ labels.sort(function(b, a) {
+ return d3.select(a).attr('y') - d3.select(b).attr('y');
+ });
+
+ labels.reverse();
+
+ var overlap_amount, label_i, label_j;
+
+ //see if each of our labels overlaps any of the other labels
+ for (var i = 0; i < labels.length; i++) {
+ //if so, nudge it up a bit, if the label it intersects hasn't already been nudged
+ label_i = d3.select(labels[i]).text();
+
+ for (var j = 0; j < labels.length; j++) {
+ label_j = d3.select(labels[j]).text();
+ overlap_amount = mg_is_vertically_overlapping(labels[i], labels[j]);
+
+ if (overlap_amount !== false && label_i !== label_j) {
+ var node = d3.select(labels[i]);
+ var newY = +node.attr('y');
+ newY = newY + overlap_amount;
+ node.attr('y', newY);
+ }
+ }
+ }
+}
+
+function mg_is_vertically_overlapping(element, sibling) {
+ var element_bbox = element.getBoundingClientRect();
+ var sibling_bbox = sibling.getBoundingClientRect();
+
+ if (element_bbox.top <= sibling_bbox.bottom && element_bbox.top >= sibling_bbox.top) {
+ return sibling_bbox.bottom - element_bbox.top;
+ }
+
+ return false;
+}
+
+function mg_is_horiz_overlap(element, sibling) {
+ var element_bbox = element.getBoundingClientRect();
+ var sibling_bbox = sibling.getBoundingClientRect();
+
+ if (element_bbox.right >= sibling_bbox.left || element_bbox.top >= sibling_bbox.top) {
+ return sibling_bbox.bottom - element_bbox.top;
+ }
+ return false;
+}
+
+function mg_is_horizontally_overlapping(element, labels) {
+ var element_bbox = element.getBoundingClientRect();
+
+ for (var i = 0; i < labels.length; i++) {
+ if (labels[i] == element) {
+ continue;
+ }
+
+ //check to see if this label overlaps with any of the other labels
+ var sibling_bbox = labels[i].getBoundingClientRect();
+ if (element_bbox.top === sibling_bbox.top &&
+ !(sibling_bbox.left > element_bbox.right || sibling_bbox.right < element_bbox.left)
+ ) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+function mg_infer_type(args, ns) {
+ // must return categorical or numerical.
+ var testPoint = mg_flatten_array(args.data);
+
+ testPoint = testPoint[0][args[ns + '_accessor']];
+ return typeof testPoint === 'string' ? 'categorical' : 'numerical';
+ }
+
+function mg_get_svg_child_of(selector_or_node) {
+ return d3.select(selector_or_node).select('svg');
+}
+
+function mg_flatten_array(arr) {
+ var flat_data = [];
+ return flat_data.concat.apply(flat_data, arr);
+}
+
+function mg_next_id() {
+ if (typeof MG._next_elem_id === 'undefined') {
+ MG._next_elem_id = 0;
+ }
+
+ return 'mg-' + (MG._next_elem_id++);
+}
+
+function mg_target_ref(target) {
+ if (typeof target === 'string') {
+ return mg_normalize(target);
+
+ } else if (target instanceof window.HTMLElement) {
+ var target_ref = target.getAttribute('data-mg-uid');
+ if (!target_ref) {
+ target_ref = mg_next_id();
+ target.setAttribute('data-mg-uid', target_ref);
+ }
+
+ return target_ref;
+
+ } else {
+ console.warn('The specified target should be a string or an HTMLElement.', target);
+ return mg_normalize(target);
+ }
+}
+
+function mg_normalize(string) {
+ return string
+ .replace(/[^a-zA-Z0-9 _-]+/g, '')
+ .replace(/ +?/g, '');
+}
+
+function get_pixel_dimension(target, dimension) {
+ return Number(d3.select(target).style(dimension).replace(/px/g, ''));
+}
+
+function get_width(target) {
+ return get_pixel_dimension(target, 'width');
+}
+
+function get_height(target) {
+ return get_pixel_dimension(target, 'height');
+}
+
+function isNumeric(n) {
+ return !isNaN(parseFloat(n)) && isFinite(n);
+}
+
+var each = function(obj, iterator, context) {
+ // yanked out of underscore
+ var breaker = {};
+ if (obj === null) return obj;
+ if (Array.prototype.forEach && obj.forEach === Array.prototype.forEach) {
+ obj.forEach(iterator, context);
+ } else if (obj.length === +obj.length) {
+ for (var i = 0, length = obj.length; i < length; i++) {
+ if (iterator.call(context, obj[i], i, obj) === breaker) return;
+ }
+ } else {
+ for (var k in obj) {
+ if (iterator.call(context, obj[k], k, obj) === breaker) return;
+ }
+ }
+
+ return obj;
+};
+
+function merge_with_defaults(obj) {
+ // taken from underscore
+ each(Array.prototype.slice.call(arguments, 1), function(source) {
+ if (source) {
+ for (var prop in source) {
+ if (obj[prop] === void 0) obj[prop] = source[prop];
+ }
+ }
+ });
+
+ return obj;
+}
+
+MG.merge_with_defaults = merge_with_defaults;
+
+function options_to_defaults(obj) {
+ return Object.keys(obj).reduce((r, k) => {
+ r[k] = obj[k][0];
+ return r;
+ }, {});
+}
+
+function compare_type(type, value) {
+ if (value == null) return true; // allow null or undefined
+ if (typeof type === 'string') {
+ if (type.substr(-2) === '[]') {
+ if (!is_array(value)) return false;
+ return value.every(i => compare_type(type.slice(0, -2), i));
+ }
+ return typeof value === type
+ || value === type
+ || type.length === 0
+ || type === 'array' && is_array(value);
+ }
+ if (typeof type === 'function') return value === type || value instanceof type;
+ return is_array(type) && !!~type.findIndex(i => compare_type(i, value));
+}
+
+function mg_validate_option(key, value) {
+ if (!is_array(MG.options[key])) return false; // non-existent option
+ const typeDef = MG.options[key][1];
+ if (!typeDef) return true; // not restricted type
+ return compare_type(typeDef, value);
+}
+
+function number_of_values(data, accessor, value) {
+ var values = data.filter(function(d) {
+ return d[accessor] === value;
+ });
+
+ return values.length;
+}
+
+function has_values_below(data, accessor, value) {
+ var values = data.filter(function(d) {
+ return d[accessor] <= value;
+ });
+
+ return values.length > 0;
+}
+
+function has_too_many_zeros(data, accessor, zero_count) {
+ return number_of_values(data, accessor, 0) >= zero_count;
+}
+
+function mg_is_date(obj) {
+ return Object.prototype.toString.call(obj) === '[object Date]';
+}
+
+function mg_is_object(obj) {
+ return Object.prototype.toString.call(obj) === '[object Object]';
+}
+
+function mg_is_array(obj) {
+ if (Array.isArray) {
+ return Array.isArray(obj);
+ }
+
+ return Object.prototype.toString.call(obj) === '[object Array]';
+}
+
+function mg_is_function(obj) {
+ return Object.prototype.toString.call(obj) === '[object Function]';
+}
+
+// deep copy
+// http://stackoverflow.com/questions/728360/most-elegant-way-to-clone-a-javascript-object
+MG.clone = function(obj) {
+ var copy;
+
+ // Handle the 3 simple types, and null or undefined
+ if (null === obj || "object" !== typeof obj) return obj;
+
+ // Handle Date
+ if (mg_is_date(obj)) {
+ copy = new Date();
+ copy.setTime(obj.getTime());
+ return copy;
+ }
+
+ // Handle Array
+ if (mg_is_array(obj)) {
+ copy = [];
+ for (var i = 0, len = obj.length; i < len; i++) {
+ copy[i] = MG.clone(obj[i]);
+ }
+ return copy;
+ }
+
+ // Handle Object
+ if (mg_is_object(obj)) {
+ copy = {};
+ for (var attr in obj) {
+ if (obj.hasOwnProperty(attr)) copy[attr] = MG.clone(obj[attr]);
+ }
+ return copy;
+ }
+
+ throw new Error("Unable to copy obj! Its type isn't supported.");
+};
+
+// give us the difference of two int arrays
+// http://radu.cotescu.com/javascript-diff-function/
+function arr_diff(a, b) {
+ var seen = [],
+ diff = [],
+ i;
+ for (i = 0; i < b.length; i++)
+ seen[b[i]] = true;
+ for (i = 0; i < a.length; i++)
+ if (!seen[a[i]])
+ diff.push(a[i]);
+ return diff;
+}
+
+MG.arr_diff = arr_diff;
+
+/**
+ Print warning message to the console when a feature has been scheduled for removal
+
+ @author Dan de Havilland (github.com/dandehavilland)
+ @date 2014-12
+*/
+function warn_deprecation(message, untilVersion) {
+ console.warn('Deprecation: ' + message + (untilVersion ? '. This feature will be removed in ' + untilVersion + '.' : ' the near future.'));
+ console.trace();
+}
+
+MG.warn_deprecation = warn_deprecation;
+
+/**
+ Truncate a string to fit within an SVG text node
+ CSS text-overlow doesn't apply to SVG <= 1.2
+
+ @author Dan de Havilland (github.com/dandehavilland)
+ @date 2014-12-02
+*/
+function truncate_text(textObj, textString, width) {
+ var bbox,
+ position = 0;
+
+ textObj.textContent = textString;
+ bbox = textObj.getBBox();
+
+ while (bbox.width > width) {
+ textObj.textContent = textString.slice(0, --position) + '...';
+ bbox = textObj.getBBox();
+
+ if (textObj.textContent === '...') {
+ break;
+ }
+ }
+}
+
+MG.truncate_text = truncate_text;
+
+/**
+ Wrap the contents of a text node to a specific width
+
+ Adapted from bl.ocks.org/mbostock/7555321
+
+ @author Mike Bostock
+ @author Dan de Havilland
+ @date 2015-01-14
+*/
+function wrap_text(text, width, token, tspanAttrs) {
+ text.each(function() {
+ var text = d3.select(this),
+ words = text.text().split(token || /\s+/).reverse(),
+ word,
+ line = [],
+ lineNumber = 0,
+ lineHeight = 1.1, // ems
+ y = text.attr("y"),
+ dy = 0,
+ tspan = text.text(null)
+ .append("tspan")
+ .attr("x", 0)
+ .attr("y", dy + "em")
+ .attr(tspanAttrs || {});
+
+ while (!!(word = words.pop())) {
+ line.push(word);
+ tspan.text(line.join(" "));
+ if (width === null || tspan.node().getComputedTextLength() > width) {
+ line.pop();
+ tspan.text(line.join(" "));
+ line = [word];
+ tspan = text
+ .append("tspan")
+ .attr("x", 0)
+ .attr("y", ++lineNumber * lineHeight + dy + "em")
+ .attr(tspanAttrs || {})
+ .text(word);
+ }
+ }
+ });
+}
+
+MG.wrap_text = wrap_text;