@domoinc/multiline-chart
Version:
MultiLineChart - Domo Widget
1,224 lines (1,074 loc) • 70.2 kB
JavaScript
/*! Copyright 2016 Domo Inc. */
(function webpackUniversalModuleDefinition(root, factory) {
if(typeof exports === 'object' && typeof module === 'object')
module.exports = factory(require("d3"), require("@domoinc/summary-number"));
else if(typeof define === 'function' && define.amd)
define(["d3", "summary-number"], factory);
else {
var a = typeof exports === 'object' ? factory(require("d3"), require("@domoinc/summary-number")) : factory(root["d3"], root["SummaryNumber"]);
for(var i in a) (typeof exports === 'object' ? exports : root)[i] = a[i];
}
})(this, function(__WEBPACK_EXTERNAL_MODULE_1__, __WEBPACK_EXTERNAL_MODULE_2__) {
return /******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId])
/******/ return installedModules[moduleId].exports;
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ exports: {},
/******/ id: moduleId,
/******/ loaded: false
/******/ };
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/ // Flag the module as loaded
/******/ module.loaded = true;
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "/dist/";
/******/ // Load entry module and return exports
/******/ return __webpack_require__(0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ function(module, exports, __webpack_require__) {
/*! 2016-02-04
* Copyright (c) 2016 Domo Apps Team;*/
var d3 = __webpack_require__(1);
var SummaryNumber = __webpack_require__(2);
var service = new SummaryNumber();
var da = {};
module.exports = da;
//**********************************************************************************
// Domo Fallback Images for avatars
// This function can be called on an image (d3 selection) and it will check to see
// if the user image has loaded. If it has not, it will display a gray box with the
// user's initials appended to the center of it.
//
// Example Usage:
// var image = d3.select('svg').append('image').attr({
// x: 0,
// y: 0,
// width: '50px',
// height: '50px'
// })
// .call(d3.avatar, '//s3.amazonaws.com/uifaces/faces/twitter/fffabs/128.jpg', 'John Doe');
//
// Note: I'm using SVG's 'getBBox()' method to determine the size of the image so this function will
// break in firefox if you image is hidden while this function is run.
//**********************************************************************************
d3.avatar = function (rawImage, url, name, initialsSize) {
// Setup Variables
var node = rawImage.node(),
image = d3.select(node),
bb = node.getBBox(),
textSize;
if (typeof initialsSize === 'number') {
textSize = initialsSize + 'px';
} else if (typeof initialsSize === 'string') {
textSize = initialsSize;
} else {
textSize = '14px';
}
// Parent Element **EACH IMAGE MUST HAVE ITS OWN PARENT**
var parent = d3.select(node.parentNode);
// Background Rect
var rect = parent.selectAll('rect.imageCover').data([name]);
rect.enter().append("rect")
.attr({
class: "imageCover",
x: bb.x,
y: bb.y,
width: bb.width,
height: bb.height
})
.style({
fill: "#e4e5e5"
});
rect.attr({
x: bb.x,
y: bb.y,
width: bb.width,
height: bb.height
});
// User initials.
var init = parent.selectAll('text.nameInitials').data([name]);
init.enter().append('text')
.text(toInit(name))
.style({
"fill": "#8a8d8f",
"font-size": textSize,
"font-weight": 400,
"font-family": "Open Sans",
"text-anchor": "middle",
"pointer-events": "none",
})
.attr({
class: "nameInitials",
x: (bb.x + bb.width / 2),
y: (bb.y + bb.height / 2),
dy: function(d) {
return parseInt(d3.select(this).style('font-size'), 10) * 0.35;
}
});
init.attr({
x: (bb.x + bb.width / 2),
y: (bb.y + bb.height / 2),
})
.text(function(d){
return toInit(d);
});
if (rawImage.tween) {
rawImage.tween('size', function() {
var fontSize = init.style('font-size');
var i = d3.interpolate(fontSize, textSize);
return function(t) {
var bb = node.getBBox();
rect.attr(bb);
init.style({
"font-size": i(t)
})
.attr({
x: (bb.x + bb.width / 2),
y: (bb.y + bb.height / 2),
dy: function(d) {
return parseInt(d3.select(this)
.style('font-size'), 10) * 0.35;
}
});
}
});
}
function onLoad() {
image.style('opacity', 1);
init.style('opacity', 0);
rect.style('opacity', 0);
}
function onError() {
image.style('opacity', 0);
init.style('opacity', 1);
rect.style('opacity', 1);
}
var newImage = new Image();
newImage.addEventListener('load', onLoad, false);
newImage.addEventListener('error', onError, false);
newImage.src = url;
image.attr("xlink:href", newImage.src);
/**
* Converts a Full name to initials
* @param {string} fullName The full name
* @return {string} The initials of the full name.
*/
function toInit(fullName) {
return fullName.split(' ')
.map(function (s) {
return s.charAt(0);
})
.join('')
.toUpperCase();
}
return this;
}
d3.browser = {
'getInternetExplorerVersion': function() {
var rv = -1;
if (navigator.appName == 'Microsoft Internet Explorer')
{
var ua = navigator.userAgent;
var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");
if (re.exec(ua) != null)
rv = parseFloat( RegExp.$1 );
}
else if (navigator.appName == 'Netscape')
{
var ua = navigator.userAgent;
var re = new RegExp("Trident/.*rv:([0-9]{1,}[\.0-9]{0,})");
if (re.exec(ua) != null)
rv = parseFloat( RegExp.$1 );
}
return rv;
}
};
//**********************************************************************************
// Returns the rgb of a color, with a given alpha and background rgb.
//**********************************************************************************
da.CalcRGBWithAlphaOnBackground = function rgbWithAlpha (rgb, alpha, backgroundRGB)
{
var r = 0;
var g = 0;
var b = 0;
//opacity*original + (1-opacity)*background
r = Math.floor(alpha*rgb.r + (1-alpha)*backgroundRGB.r);
g = Math.floor(alpha*rgb.g + (1-alpha)*backgroundRGB.g);
b = Math.floor(alpha*rgb.b + (1-alpha)*backgroundRGB.b);
return 'rgb(' + r + ',' + g + ',' + b + ')';
};
//----------------------------------------------------------------------------------
//----------------------------------------------------------------------------------
// Domo Careted Rectangle with rounded corners: This function returns a path element
// that is a function of the parameters of the input width, height, radius, caret, x, y
// if x and y are not sent in then the function will assume 0 and if other elemnt is
// missing it will assume the default // 100, 50, 5, 10,0,0))
//
// Example:
// var tooltip_group = svg.append('g')
// var bubble = tooltip_group.append('path')
// .attr('d', d3.DomoCaretRect(100, 50, 5, 10, 0, 0))
// var bubble_text = tooltip_group.append('text')
// .attr('transform', 'tranlate(0, -25)')
// .style("text-anchor", "middle")
// .text('Sample')
//----------------------------------------------------------------------------------
//----------------------------------------------------------------------------------
d3.DomoCaretRect = function (width, height, radius, caret, x, y){
//**********************************************************************************
// Generation Function: This function returns a rounded Rect with a caret on the
// bottom, it is used to create the pop up bubble
//**********************************************************************************
if(x === undefined){
x = 0;
}
if(y === undefined){
y = 0;
}
if(width === undefined){
width = 100;
}
if(height === undefined){
height = 50;
}
if(radius === undefined){
radius = 5;
}
if(caret === undefined){
caret = 10;
}
return "M" + (x) + "," + (y) +
"l" + ""+ -caret/2 + " " + -(caret/2) +
"h" + ((radius) - (width/2 - (caret)/2)) +
"a" + radius + "," + radius + " 0 0 1 " + -radius +"," + -radius +
"v" + (-height + (2*radius)) +
"a" + radius + "," + radius + " 0 0 1 " + radius +"," + -radius +
"h" + (width - (2*radius)) +
"a" + radius + "," + radius + " 0 0 1 " + radius +"," + radius +
"v" + (height - (2*radius)) +
"a" + radius + "," + radius + " 0 0 1 " + -radius +"," + radius +
"h" + ((radius) - (width/2 - (caret)/2)) +
"l" + ""+ -caret/2 + " " + (caret/2) +
"z";
}
//----------------------------------------------------------------------------------
//----------------------------------------------------------------------------------
// CatigoryIndexGenerator Generator:
// This extension generates categories for data. You send in the Parameters
// number of categories and the range for min and max. The function then generates
// the correlating number of categories based of the range and appends a no
// data catigory to the beginning.
//
// Detailed Explaination: This means that if you requested 4 categories you’ll
// actually get 5, with the zero catigory being -2 to zero, the 1 catigory containing
// your min, and so on with the 5 catigory containing your max.
//
// After calling this function it returns an object containing a 2d array, you can
// then call the following functions on the object:
// 1. GetCatigoryIndexForValue(value) : which allows you to get the catigory
// for any value that you send to the object
// 2. UpdateCatigoryRanges(newNumCategories, newDataValueRange): Essentially
// refactors the catigory object with a new number of categories and a new
// data value range.
//
// Example:
// var cat_object = d3.DomoCatigoryIndexGenerator(4, [0,100])
// // cat_object is now initialized
// cat_object.getCatigoryIndexForValue(25)
// // returns 2
// cat_object.getCatigoryIndexForValue(50)
// // returns 3
// cat_object.getCatigoryIndexForValue(49.99)
// // returns 2
// cat_object.getCatigoryIndexForValue(5000)
// // returns -1
// console.log(cat_object.rangeForEachCatigory)
// //Logs [ [-2, 0], [0, 25], [25, 50], [50, 75], [75, 101] ]
// // on Evaluation it's Greater than or equal to the bottom but always less than the top
//
//
//----------------------------------------------------------------------------------
//----------------------------------------------------------------------------------
d3.DomoCatigoryIndexGenerator = function(numberOfCatigories, dataRange) {
//**********************************************************************************
// Returns an array of ranges. One range for each 'numCategories' given the
// the min and max values specified in dataValueRange.
// dataValueRange == [min, max]
// numCategories == 5
//**********************************************************************************
function genRangesInEachColorCatigory(dataValueRange, numCategories) {
var rangesInEachColorCatigory = [];
rangesInEachColorCatigory.push([-2, 0]);
var deltaStep = Math.floor((dataValueRange[1] - dataValueRange[0]) / numCategories);
var stepVal = dataValueRange[0];
//Append Catigory labels
for (var i = 0; i < numCategories - 1; i++) {
rangesInEachColorCatigory.push([stepVal, stepVal + deltaStep]);
stepVal += deltaStep;
}
//Append Top Catigory label
rangesInEachColorCatigory.push([stepVal, dataValueRange[1] + 1]);
return rangesInEachColorCatigory;
}
//----------------------------------------------------------------------------------
//----------------------------------------------------------------------------------
var cateGenerator = {
numCategories: numberOfCatigories,
range: dataRange,
rangeForEachCatigory: null,
//**********************************************************************************
// getCatigoryIndexForValue: This function will convert a value within a given
// range into and index.
//**********************************************************************************
getCatigoryIndexForValue: function(value) {
if (cateGenerator.rangeForEachCatigory == null)
cateGenerator.rangeForEachCatigory = genRangesInEachColorCatigory(cateGenerator.range, cateGenerator.numCategories);
if (value == undefined) return 0;
for (var i = 0; i < cateGenerator.rangeForEachCatigory.length; i++) {
if (cateGenerator.rangeForEachCatigory[i][0] <= value && value < cateGenerator.rangeForEachCatigory[i][1])
return i;
}
console.log("Error: returning index of catigory wasn't found for value:" + value);
return -1;
},
//**********************************************************************************
// Updates the catIndexGenerators valueRange, numberOfCatigories, and
// the array of ranges in each catigory.
// IE: call on data update
//**********************************************************************************
updateCatigoryRanges: function(newNumCategories, newDataValueRange) {
cateGenerator.range = newDataValueRange;
cateGenerator.numCategories = newNumCategories;
cateGenerator.rangeForEachCatigory = genRangesInEachColorCatigory(newDataValueRange, newNumCategories);
},
};
return cateGenerator;
};
//----------------------------------------------------------------------------------
//----------------------------------------------------------------------------------
// ClipPath Generator:
// Example:
// usmChart._clip = d3.DomoClipPath();
// usmChart._clip.appendNewDefsAndDomoClipPath(usmChart._height,usmChart._width,usmChart.base);
// usmChart.base.attr("clip-path", "url(#" + usmChart._clip.getTheRecentlyAppendedDomoClipPathsId() + ")");
//
//----------------------------------------------------------------------------------
//----------------------------------------------------------------------------------
d3.DomoClipPath = function()
{
var CLIP_ID = "DomoClipPath";
//**********************************************************************************
// Searches Dom for a clipId. Return the clipId + (number of id's found plus one)
// Thus generating a unique clipId.
//**********************************************************************************
function generateNewUniqueDomoClipPathId()
{
var numClipIds = d3.selectAll("[id^="+CLIP_ID+"]").size();
return CLIP_ID + "_" + numClipIds;
}
//----------------------------------------------------------------------------------
//----------------------------------------------------------------------------------
var clipIt =
{
clipsId: null,
recentRect: null,
//**********************************************************************************
// Appends a <defs> and <clippath> to the container.
//**********************************************************************************
appendNewDefsAndDomoClipPath: function (height, width, container)
{
clipIt.clipsId = generateNewUniqueDomoClipPathId();
clipIt.recentRect = container.append("defs")
.append("clipPath")
.attr("id", clipIt.clipsId)
.append("rect")
.attr("x", 0)
.attr("y", 0);
clipIt.updateHeightAndWidthOfClippingRect(height, width);
return clipIt;
},
//**********************************************************************************
// Return the id that was generated during the last call to
// appendNewDefsAndDomoClipPath
//**********************************************************************************
getTheRecentlyAppendedDomoClipPathsId: function () { return clipIt.clipsId; },
//**********************************************************************************
// This function will update the cliping rectangle height and width.
//**********************************************************************************
updateHeightAndWidthOfClippingRect: function (height, width)
{
clipIt.height = (height < 0 ? 0 : height);
clipIt.width = (width < 0 ? 0 : width);
if (clipIt.recentRect == null) {console.log("clipIt recentRect not set."); return;}
clipIt.recentRect.attr("height", clipIt.height)
.attr("width", clipIt.width);
},
};
return clipIt;
};
//**********************************************************************************
// Returns primary and secondary color ranges in an object so you can use the correct
// color range given the input parameter of series count
// Example:
// var color_range = d3.DomoColorRange(5)
// //Now color Range is an object containing two keys 'primary', and 'secondary' using
// //the key will give you access to the array of colors.
// //secondary colors are often used for text elements on the correcting primary color
//
//**********************************************************************************
d3.DomoColorRange = function(seriesCount){
if(seriesCount <=4){
return {primary : ["#73B0D7","#BBE491","#FB8D34","#E45621"].slice(0,seriesCount),
secondary : ["#D9EBFD","#387B26","#FDECAD","#FDECAD"].slice(0,seriesCount)};
}else if(seriesCount <= 17){
var colorRanges = {
5 : {primary : ["#73B0D7","#A0D771","#559E38","#FBAD56","#E45621"],
secondary : ["#D9EBFD","#DDF4BA","#DDF4BA","#FDECAD","#FDECAD"]},
6 : {primary : ["#90c4e4","#4E8CBA","#A0D771","#559E38","#FBAD56","#E45621"],
secondary : ["#D9EBFD","#D9EBFD","#DDF4BA","#DDF4BA","#FDECAD","#FDECAD"]},
7 : {primary : ["#90c4e4","#4E8CBA","#A0D771","#559E38","#FCCF84","#E45621","#FB8D34"],
secondary : ["#D9EBFD","#D9EBFD","#DDF4BA","#DDF4BA","#A43724","#FDECAD","#FDECAD"]},
8 : {primary : ["#90c4e4","#4E8CBA","#BBE491","#559E38","#80C25D","#FCCF84","#E45621","#FB8D34"],
secondary : ["#D9EBFD","#D9EBFD","#387B26","#DDF4BA","#DDF4BA","#A43724","#FDECAD","#FDECAD"]},
9 : {primary : ["#B7DAF5","#4E8CBA","#73B0D7","#BBE491","#559E38","#80C25D","#FCCF84","#E45621","#FB8D34"],
secondary : ["#31689B","#D9EBFD","#D9EBFD","#387B26","#DDF4BA","#DDF4BA","#A43724","#FDECAD","#FDECAD"]},
10 : {primary : ["#B7DAF5","#4E8CBA","#73B0D7","#BBE491","#559E38","#80C25D","#FDECAD","#FB8D34","#FCCF84","#E45621"],
secondary : ["#31689B","#D9EBFD","#D9EBFD","#387B26","#DDF4BA","#DDF4BA","#E45621","#FDECAD","#A43724","#FDECAD"]},
11 : {primary : ["#B7DAF5","#4E8CBA","#73B0D7","#DDF4BA","#80C25D","#BBE491","#559E38","#FDECAD","#FB8D34","#FCCF84","#E45621"],
secondary : ["#31689B","#D9EBFD","#D9EBFD","#559E38","#DDF4BA","#387B26","#DDF4BA","#E45621","#FDECAD","#A43724","#FDECAD"]},
12 : {primary : ["#D9EBFD","#73B0D7","#B7DAF5","#4E8CBA","#DDF4BA","#80C25D","#BBE491","#559E38","#FDECAD","#FB8D34","#FCCF84","#E45621"],
secondary : ["#4E8CBA","#D9EBFD","#31689B","#D9EBFD","#559E38","#DDF4BA","#387B26","#DDF4BA","#E45621","#FDECAD","#A43724","#FDECAD"]},
13 : {primary : ["#D9EBFD","#73B0D7","#B7DAF5","#4E8CBA","#DDF4BA","#80C25D","#BBE491","#559E38","#FDECAD","#FB8D34","#FCCF84","#E45621","#FBAD56"],
secondary : ["#4E8CBA","#D9EBFD","#31689B","#D9EBFD","#559E38","#DDF4BA","#387B26","#DDF4BA","#E45621","#FDECAD","#A43724","#FDECAD","#FDECAD"]},
14 : {primary : ["#D9EBFD","#73B0D7","#B7DAF5","#4E8CBA","#DDF4BA","#80C25D","#BBE491","#559E38","#A0D771","#FDECAD","#FB8D34","#FCCF84","#E45621","#FBAD56"],
secondary : ["#4E8CBA","#D9EBFD","#31689B","#D9EBFD","#559E38","#DDF4BA","#387B26","#DDF4BA","#DDF4BA","#E45621","#FDECAD","#A43724","#FDECAD","#FDECAD"]},
15 : {primary : ["#D9EBFD","#73B0D7","#B7DAF5","#4E8CBA","#90c4e4","#DDF4BA","#80C25D","#BBE491","#559E38","#A0D771","#FDECAD","#FB8D34","#FCCF84","#E45621","#FBAD56"],
secondary : ["#4E8CBA","#D9EBFD","#31689B","#D9EBFD","#D9EBFD","#559E38","#DDF4BA","#387B26","#DDF4BA","#DDF4BA","#E45621","#FDECAD","#A43724","#FDECAD","#FDECAD"]},
16 : {primary : ["#D9EBFD","#73B0D7","#B7DAF5","#4E8CBA","#90c4e4","#DDF4BA","#80C25D","#BBE491","#559E38","#A0D771","#FDECAD","#FB8D34","#FCCF84","#E45621","#FBAD56","#A43724"],
secondary : ["#4E8CBA","#D9EBFD","#31689B","#D9EBFD","#D9EBFD","#559E38","#DDF4BA","#387B26","#DDF4BA","#DDF4BA","#E45621","#FDECAD","#A43724","#FDECAD","#FDECAD","#FDECAD"]},
17 : {primary : ["#D9EBFD","#73B0D7","#B7DAF5","#4E8CBA","#90c4e4","#DDF4BA","#80C25D","#BBE491","#559E38","#A0D771","#387B26","#FDECAD","#FB8D34","#FCCF84","#E45621","#FBAD56","#A43724"],
secondary : ["#4E8CBA","#D9EBFD","#31689B","#D9EBFD","#D9EBFD","#559E38","#DDF4BA","#387B26","#DDF4BA","#DDF4BA","#DDF4BA","#E45621","#FDECAD","#A43724","#FDECAD","#FDECAD","#FDECAD"]},
};
return colorRanges[seriesCount];
}else{
return {primary : ["#D9EBFD","#73B0D7","#B7DAF5","#4E8CBA","#90c4e4","#31689B","#DDF4BA","#80C25D","#BBE491","#559E38","#A0D771","#387B26","#FDECAD","#FB8D34","#FCCF84","#E45621","#FBAD56","#A43724","#F3E4FE","#B391CA","#DDC8EF","#8F6DC0","#C5ACDE","#7940A1","#FCD7E6","#EE76BF","#FBB6DD","#CF51AC","#F395CD","#A62A92","#D8F4DE","#68BEA8","#ABE4CA","#46998A","#8DD5BE","#227872", "#FDDDDD","#FCBCB7","#FD9A93","#FD7F76","#E45850","#C92E25", "#F1F2F2","#CACBCC","#B0B1B2","#959899","#7B7E80","#54585A"].slice(0,seriesCount),
secondary : ["#4E8CBA","#D9EBFD","#31689B","#D9EBFD","#D9EBFD","#D9EBFD","#559E38","#DDF4BA","#387B26","#DDF4BA","#DDF4BA","#DDF4BA","#E45621","#FDECAD","#A43724","#FDECAD","#FDECAD","#FDECAD","#8F6DC0","#F3E4FE","#7940A1","#F3E4FE","#F3E4FE","#F3E4FE","#CF51AC","#FCD7E6","#A62A92","#FCD7E6","#FCD7E6","#FCD7E6","#46998A","#D8F4DE","#227872","#D8F4DE","#D8F4DE","#D8F4DE", "#FD7F76","#E45850","#C92E25","#FDDDDD","#FCBCB7","#FD9A93", "#959899","#7B7E80","#54585A","#F1F2F2","#CACBCC","#B0B1B2"].slice(0,seriesCount)};
}
};
//----------------------------------------------------------------------------------
//----------------------------------------------------------------------------------
// DomoFormatted Number Director:
// This util can append a Domo formatted number and set its contents.
//
// Example: (http://jsfiddle.net/chrisclm09/gXjB5/)
// var test = d3.select("#test");
// var t = d3.appendDomoFormattedNumberTextElementToContainer(test);
// d3.setDomoFormattedTextElement(t, "$", 100000, 18, "DAYS", 10);
//----------------------------------------------------------------------------------
//----------------------------------------------------------------------------------
//**********************************************************************************
// This function will append a text element to the given container with two
// inner tspans for formatting later.
//**********************************************************************************
d3.appendDomoFormattedNumberTextElementToContainer = function(container)
{
var textElement = container.append("text");
textElement.append("tspan").attr("id", "largeTspan").classed("largeTspan", true);
textElement.append("tspan").attr("id", "smallTspan").classed("smallTspan", true);
return textElement;
};
//**********************************************************************************
// This function will set a DomoFormattedTextElement's text and size.
// Prefix: Something to append to the front of the number, like $
// prefixSize: Size of the prefix text and the number.
// number: number to be show, this will be formatted.
// postfix: Something to be appended to the end of the number, like 'DAYS' or '%'
// If there is no magnitude on the number i.e.:(10.0K) there will be no space
// between the number and postfix: (10%) (10DAYS).
// If there is a magnitude on the number a space will be added to separate
// the magnitude and the postfix, as long as the postfix doesn't start with
// a space.
// Example: (magnitude, postfix) -> output
// '' , 'DAYS' -> 10DAYS
// 'K' , 'DAYS' -> 10K DAYS
// '' , ' DAYS' -> 10 DAYS
// 'K' , ' DAYS' -> 10K DAYS
// postfixSize: Size of the magnitude if appended and the postfix string.
//
// Example:
// (dom, '$', 1000, 18, 'USA', 10) -> "$1.00K USA"
//**********************************************************************************
d3.setDomoFormattedTextElement = function(domoElement, prefix, number, prefixSize, postfix, postfixSize)
{
var largeTspan = domoElement.select("#largeTspan");
var smallTspan = domoElement.select("#smallTspan");
var numberAndUnit = service.summaryNumberToObject(number);
largeTspan
.style("font-size", (prefixSize + 'px'))
.text(prefix + String(numberAndUnit.prefix));
// smallTspan
// .style("font-size", postfixSize)
// .text(numberAndUnit.magnitude +
// ((numberAndUnit.magnitude == '' ||
// (postfix && postfix.length && postfix[0] == ' ')) ? '' : ' ') +
// postfix);
smallTspan
.style("font-size", (postfixSize + 'px'))
.text(numberAndUnit.magnitude + ' ' + postfix);
};
//**********************************************************************************
// Return a str formatted number.
// Example:
// 1000 -> '1.00K'
//**********************************************************************************
d3.DomoFormatNumber = function (val)
{
return service.summaryNumber(val);
}
//../lib/moment.min.js required.
//**********************************************************************************
// This function will return an array of formatted dates between a given
// date range.
// Example:
// [new Date(2014, 0, 1), new Date(2014, 0, 9)] ->
//**********************************************************************************
//**********************************************************************************
// This function will determine the font size for a string given the amount of
// room available to show it.
// textElement: the selection of the text.
// returns: font-size (i.e. "14px" - "0px")
// Example:
// bubblesGroup.append("text").attr("class", "bubbleTitle")
// .attr("fill", "black")
// .text("Hey you!")
// .attr("font-size", function (d)
// {
// return d3.DomoGetFontSizeToFitTextInBounds(d3.select(this), 10, 10);
// })
// .
// .
//
//**********************************************************************************
// d3.DomoGetFontSizeToFitTextInBounds = function(textElement, width, height)
// {
// var possibleTextSizes = ["14px", "12px", "10px", "8px", "0px"];
// var pTextIndex = 0;
// var padding = 5;
// var tempBox = null;
// while (pTextIndex != possibleTextSizes.length - 1)
// {
// //Try a font size
// textElement.attr("font-size", possibleTextSizes[pTextIndex]);
// tempBox = textElement.node().getBBox();
// //Check if it fits
// if (tempBox.width < width - padding &&
// tempBox.height < height - padding)
// break; //Got it
// else
// pTextIndex++; //Try the next smaller size.
// }
// return possibleTextSizes[pTextIndex];
// };
// this edit makes it possible to have a 0px size returned, and allows you to pass a starting
// size e.g. startingSize = "10px" will return text at 10px or smaller.
d3.DomoGetFontSizeToFitTextInBounds = function(textElement, width, height, startingSize)
{
var possibleTextSizes = ["14px", "10px", "8px", "0.1px"];
var pTextIndex = possibleTextSizes.indexOf( startingSize ) || 0;
var padding = 5;
var tempBox = null;
while (pTextIndex < possibleTextSizes.length)
{
//Try a font size
textElement.style("font-size", possibleTextSizes[pTextIndex]);
tempBox = textElement.node().getBBox();
//Check if it fits
if (tempBox.width < width - padding &&
tempBox.height < height - padding)
break; //Got it
else
pTextIndex++; //Try the next smaller size.
}
return possibleTextSizes[pTextIndex];
};
//**********************************************************************************
// Same as above but let you pick the start size.
// It will start as the passed in size and decrement that size by 2 until
// the text fits in the passed in width and height minus a small amount of padding.
//**********************************************************************************
d3.DomoGetFontSizeToFitTextInBoundsStartAtSize = function(textElement, width, height, size)
{
var padding = 5;
var tempBox = null;
while (size > 0)
{
//Try a font size
textElement.style("font-size", size + "px");
tempBox = textElement.node().getBBox();
//Check if it fits
if (tempBox.width < width - padding &&
tempBox.height < height - padding)
break; //Got it
else
size = size - 2; //Try the next smaller size.
}
return (size < 0 ? 0 : size);
};
d3.domoIcons = {
"pencil": function ( element ) {
element.append("path")
.style("fill", "#CBCBCC")
.attr({
"d": "M10.2,2.9l-8.8,8.8h0l0,0h0l0,0L0,16l4.3-1.4l0,0l0,0l0,0l0,0l8.8-8.8L10.2,2.9z M3.4,12.1l-0.5-0.5l7.2-7.2l0.5,0.5L3.4,12.1z",
});
element.append("rect")
.attr({
"x": 12.2,
"y": 0.4,
"width": 3.3,
"height":4.0,
"transform": "matrix(0.707 -0.7072 0.7072 0.707 2.2664 10.3121)",
})
.style({
"fill" : "#CBCBCC",
"width" : 2.7,
"height" : 4.1
});
element.append("rect")
.attr({
"width" : 17,
"height" : 17,
"opacity" : 0.0,
})
.style({
"fill" : "#ffddff"
})
return element;
},
"funnel": function ( element ) {
// Funnel by Volodin Anton from The Noun Project
element.append("path")
.style({
"fill": "#444",
})
.attr({
"d": "M87.5,27.292c0-6.944-16.789-12.576-37.501-12.576c-20.71,0-37.499,5.632-37.499,12.576c0,0.148,0.018,0.294,0.033,0.441 c-0.006,0.035-0.033,0.046-0.033,0.09c0,0.665,0,1.77,0,2.454c0,0.686,0.324,1.571,0.72,1.968c0.396,0.396,1.441,1.445,2.325,2.331 l27.111,27.188c0,8.948,0,19.865,0,20.237c0,0.634,1.196,3.283,7.345,3.283c6.147,0,7.343-2.489,7.343-3.283 c0-0.467,0-11.375,0-20.294l27.399-27.424c0.883-0.886,1.865-1.868,2.183-2.184c0.314-0.317,0.574-1.043,0.574-1.615 s0-1.678,0-2.455c0-0.069-0.034-0.095-0.046-0.149C87.481,27.685,87.5,27.489,87.5,27.292z M49.999,20.472 c18.605,0,29.207,4.472,31.473,6.82c-2.266,2.349-12.867,6.82-31.473,6.82c-18.604,0-29.205-4.472-31.47-6.82 C20.794,24.943,31.395,20.472,49.999,20.472z",
});
return element;
},
"magnifyingGlass": function ( element ) {
// <path d="M16.6,15.1c1.2-1.6,2-3.5,2-5.6c0-5-4.1-9.1-9.1-9.1 S0.4,4.4,0.4,9.4s4.1,9.1,9.1,9.1c2.1,0,4.1-0.7,5.6-2l5.1,5.1c0.4,0.4,1,0.4,1.4,0l0.1-0.1c0.4-0.4,0.4-1,0-1.4L16.6,15.1z M9.5,16.6c-4,0-7.2-3.2-7.2-7.2s3.2-7.2,7.2-7.2s7.2,3.2,7.2,7.2S13.4,16.6,9.5,16.6z" style="fill: rgb(138, 141, 143);"></path>
element.append("path")
.style({
"fill": "rgb(138, 141, 143)",
})
.attr({
"d": "M16.6,15.1c1.2-1.6,2-3.5,2-5.6c0-5-4.1-9.1-9.1-9.1 S0.4,4.4,0.4,9.4s4.1,9.1,9.1,9.1c2.1,0,4.1-0.7,5.6-2l5.1,5.1c0.4,0.4,1,0.4,1.4,0l0.1-0.1c0.4-0.4,0.4-1,0-1.4L16.6,15.1z M9.5,16.6c-4,0-7.2-3.2-7.2-7.2s3.2-7.2,7.2-7.2s7.2,3.2,7.2,7.2S13.4,16.6,9.5,16.6z",
});
return element;
},
"diamond": function(element, attr) {
element.append('polygon')
.attr('points', '4,0 0,5 4,10 8,5');
if(attr === undefined) {
element.attr('fill', '#CBCBCC');
} else {
element.attr(attr);
}
return element
},
"help": function () {
console.log( "d3.DomoIcons.pencil( element ): Draws a light grey pencil icon and returns the element passed in.\n" +
"Example:\n" +
"\td3.DomoIcons.pencil( d3.select('g') )\n" +
"\t\t.attr('transform', 'translate(10,10)');" );
}
}
//----------------------------------------------------------------------------------
//----------------------------------------------------------------------------------
// Number Cleanup:
// This function will take a number and format it to something with up to 3 digits
// and a decimal point, as a result of this process something needs an abbreviation,
// so 10000 becomes 10K, 10100 become 10.1K, and 10010 becomes 10.01K.
// Thif function works regardless of if the number is a string or
// if the param is an actual integer value
//
// Example:
// var formattedNum = d3.DomoNumberCleanup(10000)
// // returns “10K”
// formattedNum = d3.DomoNumberCleanup(“10000”)
// // returns “10K”
// formattedNum = d3.DomoNumberCleanup(“1100000”)
// // returns “1.1M"
//
// Test Cases:
// Tested on PI
// Tested on random numbers using a function like this: function getRandomInt(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min;}
// Tested on 10000, 1100000, 10010, 10100
//----------------------------------------------------------------------------------
//----------------------------------------------------------------------------------
d3.DomoNumberCleanup = function (number, numOfDigits){
if (numOfDigits === undefined){
numOfDigits === 3
}
var cleanedNum = d3.DomoFormatNum((+number), numOfDigits)
return cleanedNum[0] + cleanedNum [1];
};
//**********************************************************************************
// Formatting Function: This function (combined with d3.DomoShortenNum function below)
// originally came from James. I've heavily modified it to take a number and format it to
// something with up to 3 digits and a decimal point, as a result of this process
// something needs an abbreviation, so 10000 becomes 10K 10100 become 10.1K
// and 10010 becomes 10.0K.
//
// There is NO rounding.
// This function operates on the idea that truncation doesn't falsely represent data
//
//
// formatNum finds the value of the number and as long as it is lower than a Quintillion
// it will call the function shortenNum on the value divided by highest possible whole
// value either 10 to the 6th 10 to the 9th etc. up to ten to the 15th
// returns array with number at index 0 and magnitude at index 1
//**********************************************************************************
d3.DomoFormatNum = function(num, numToShow){
if (numToShow === undefined){
numToShow = 3
}
if( num >= 1000000000000000){
return shortenNum((+num/1000000000000000), "Q", numToShow);
}else if(num >= 1000000000000){
return shortenNum((+num/1000000000000), "T", numToShow);
}else if(num >= 1000000000){
return shortenNum((+num/1000000000), "B", numToShow);
}else if(num >= 1000000){
return shortenNum((+num/1000000), "M", numToShow);
}else if(num >= 1000){
return shortenNum((+num/1000), "K", numToShow);
}else{
return shortenNum(+num, '', numToShow);
}
//**********************************************************************************
// ShortenNum function:
// 1. Converts number to a string and stores the original decimal value
// 2. If there is no decimal value then the number is just returned (for example) 1000 is 1K
// 3. Uses the decimal to break the string into two parts
// 4. combines the two parts without the decimal
// 5. loops through two parts to truncate it down to 3 characters or less (decimal is not included)
// 5a. loop is prevented from going to infinity by capping the amount of loops possible at 5000
// 6. Period is added back into it's original position
// 7. If the last character is a period it removes the period
// 8. Add the abbreviation sent in from the format number function
//**********************************************************************************
function shortenNum (number, abbrev, numberToShow){
if (numberToShow === undefined || numberToShow < 3){
numberToShow = 3
}
var numberString = number + ''
var indexOfPeriod = numberString.indexOf('.')
if (indexOfPeriod === -1){
return [numberString, abbrev]
}
var everythingAfterDecimal = numberString.substring(indexOfPeriod + 1, numberString.length)
if (everythingAfterDecimal.length > 2){
everythingAfterDecimal = everythingAfterDecimal.substring(0, 2)
}
var everythingBeforeDecimal = numberString.substring(0, indexOfPeriod);
var result = everythingBeforeDecimal + everythingAfterDecimal
var infinitePreventer = 0
while (result.length > numberToShow && infinitePreventer < 5000){
var length = result.length -1
result = result.substring(0, length)
infinitePreventer ++;
}
result = result.substring(0, indexOfPeriod) + '.' + result.substring(indexOfPeriod, result.length)
if ((result.length - 1) === result.indexOf('.')){
result = result.substring(0, result.length - 1)
}
return [result, abbrev]
}
};
//----------------------------------------------------------------------------------
//----------------------------------------------------------------------------------
// Round any corner of a rectangle you want:
// Usage: append a path and use this function to build the "d" attribute.
// Parameters:
// x: x-coordinate
// y: y-coordinate
// w: width
// h: height
// r: corner radius
// tl: top_left rounded?
// tr: top_right rounded?
// bl: bottom_left rounded?
// br: bottom_right rounded?
// example:
/*\
|*|
|*| d3.select("path")
|*| .attr({
|*| "d": function (d, i) {
|*| return d3.roundedRect( 0, 0, 100, 100, 5, true, false, false, true );
|*| }
|*| });
|*|
\*/
//----------------------------------------------------------------------------------
//----------------------------------------------------------------------------------
d3.roundedRect = function(x, y, w, h, r, tl, tr, bl, br)
{
var retval;
retval = "M" + (x + r) + "," + y;
retval += "h" + (w - 2*r);
if (tr) { retval += "a" + r + "," + r + " 0 0 1 " + r + "," + r; }
else { retval += "h" + r; retval += "v" + r; }
retval += "v" + (h - 2*r);
if (br) { retval += "a" + r + "," + r + " 0 0 1 " + -r + "," + r; }
else { retval += "v" + r; retval += "h" + -r; }
retval += "h" + (2*r - w);
if (bl) { retval += "a" + r + "," + r + " 0 0 1 " + -r + "," + -r; }
else { retval += "h" + -r; retval += "v" + -r; }
retval += "v" + (2*r - h);
if (tl) { retval += "a" + r + "," + r + " 0 0 1 " + r + "," + -r; }
else { retval += "v" + -r; retval += "h" + r; }
retval += "z";
return retval;
}
//To use this utility, pass in any container with paths inside of it and it will convert the paths to a dot
//matrix. It works really well on maps, but I haven't implemented a way for it to work with zoomable maps yet.
//Anybody?
d3.shapeToGrid = function(container){
var allPaths = container.selectAll("path"),
i = 0,
defs = d3.select('#defs');
allPaths.each(function(){
i++;
var bb = this.getBBox(),
svgBB = container.node().getBBox(),
path = d3.select(this).attr("d"),
color = d3.select(this).style("fill"),
parent = d3.select(this.parentNode),
defsClip = defs.append("clipPath").attr("id","clip"+i).append("path").attr('d',path),
group = parent.append('g').attr('clip-path',"url(#clip"+i+")"),
xCount = Math.ceil(bb.width / 5)+1,
yCount = Math.ceil(bb.height / 5)+1,
newX = Math.floor(bb.x/5)*5,
newY = Math.floor(bb.y/5)*5;
for (var cy = 0; cy < yCount; cy++){
for (var cx = 0; cx < xCount; cx++){
group.append("circle").attr("cx",cx*5+2.5+newX).attr("cy",cy*5+2.5+newY).attr("r",2).style("fill",color);
}
}
d3.select(this).remove();
});
}
// d3.domoStrings is an object of functions that will alter strings with font shrinking and truncating.
// trunc(string, characters, useWordBoundary): The string to modify and the characters you want to truncate to.
// Ex: .text(function(d){ return d3.domoStrings.trunc(d.name, 25)
// truncToFit(textElement, width): textElement is the d3 selection of the text element and width is the width that you want the element to fit inside after truncating.
// Ex: d3.selectAll("text").each(function(){ return d3.domoStrings.truncToFit(d3.select(this), 45); });
// shrinkToFit(textElement, width, height, size): textElement is the d3 selection of the text element, width is the target width to shrink to, height is the target height, size is the starting size of the font.
// Ex: Ex: d3.selectAll("text").each(function(){ return d3.domoStrings.shrinkToFit(d3.select(this), 45, 25, 10); });
// shrinkToTrunc(textElement, width, size): textElement is the d3 selection of the text element, width is the target width to shrink to and size is used if you want to define a starting font size to shrink from. If size is null sizes start at 14
// Ex: d3.selectAll("text").each(function(){ return d3.domoStrings.truncToFit(d3.select(this), 45, 18); });
d3.domoStrings = {
'trunc': function(str, chars, useWordBoundary) {
var toLong = str.length > chars,
s_ = toLong ? str.substr(0, chars - 1) : str;
s_ = useWordBoundary && toLong ? s_.substr(0, s_.lastIndexOf(' ')) : s_;
s_ = toLong ? s_ + '...' : s_;
return s_;
},
'truncToFitVertical': function(textElement, height) {
padding = 5;
var wasEdited = false;
str = textElement.text();
tempBox = textElement.node()
.getBoundingClientRect();
for (var i = str.length; i >= 0; i--) {
textElement
.text(str)
tempbox = textElement.node()
.getBoundingClientRect();
if (height - padding < tempbox.height) {
str = str.substr(0, str.length - 1)
wasEdited = true;
} else {
break;
}
}
if (wasEdited === true) {
str = str.substr(0, str.length - 1) + '...';
textElement.text(str);
} else {
return;
}
},
'truncToFit': function(textElement, width) {
padding = 5;
var wasEdited = false;
str = textElement.text();
tempBox = textElement.node()
.getBoundingClientRect();
for (var i = str.length; i >= 0; i--) {
textElement
.text(str)
tempbox = textElement.node()
.getBoundingClientRect();
if (width - padding < tempbox.width) {
str = str.substr(0, str.length - 1)
wasEdited = true;
} else {
break;
}
}
if (wasEdited === true) {
str = str.substr(0, str.length - 1) + '...';
textElement.text(str);
} else {
return;
}
},
'shrinkToFit': function(textElement, width, height, size) {
var padding = 5,
tempBox;
if (size) {
while (size > 6) {
//Try a font size
textElement.style("font-size", size + "px");
tempBox = textElement.node()
.getBoundingClientRect();
//Check if it fits
if (tempBox.width < width - padding &&
tempBox.height < height - padding)
break;
else
size = size - 2;
}
if (size < 8) {
textElement.style("font-size", "0px");
}
} else {
var possibleTextSizes = ["14px", "12px", "10px", "8px", "0px"];
var pTextIndex = 0;
while (pTextIndex !== possibleTextSizes.length - 1) {
//Try a font size
textElement.style("font-size", possibleTextSizes[pTextIndex] + 'px');
tempBox = textElement.node()
.getBoundingClientRect();
//Check if it fits
if (tempBox.width < width - padding &&
tempBox.height < height - padding)
break; //Got it
else
pTextIndex++; //Try the next smaller size.
}
if (pTextIndex === possibleTextSizes.length - 1) {
textElement.style("font-size", "0px");
}
}
},
'shrinkToTrunc': function(textElement, width, height, size, useWordBoundary) {
str = textElement.text();
var tempBox,
padding = 5;
if (size) {
while (size > 6) {
//Try a font size
textElement.style("font-size", size + "px");
tempBox = textElement.node()
.getBoundingClientRect();
//Check if it fits
if (tempBox.width < width - padding &&
tempBox.height < height - padding)
break;
else
size = size - 2;
}
if (size < 8) //still doesnt fit with just font shrinking, now to truncate
{
textElement.style("font-size", "8px");
d3.domoStrings.truncToFit(textElement, width)
}
} else {
var possibleTextSizes = ["14px", "12px", "10px", "8px", "0px"];
var pTextIndex = 0;
while (pTextIndex !== possibleTextSizes.length - 1) {
//Try a font size
textElement.style("font-size", possibleTextSizes[pTextIndex] + 'px');
tempBox = textElement.node()
.getBoundingClientRect();
//Check if it fits
if (tempBox.width < width - padding &&
tempBox.height < height - padding)
break; //Got it
else
pTextIndex++; //Try the next smaller size.
}
//Once we hit the end of our array of sizes, start truncating
if (pTextIndex === possibleTextSizes.length - 1) {
//once again set font size to 8 to begin with
textElement.style("font-size", "8px");
d3.domoStrings.truncToFit(textElement, width)
}
}
},
'wrapText': function(textElement, width) {
var padding = 5;
var str = textElement.text();
var dy = textElement.node()
.getBoundingClientRect()
.height;
var words = str.split(' ');
if (words.length <= 1) {
return;
}
var lines = [];
var curLine = words[0];
var testLine = curLine;
for (var i = 1; i < words.length; i++) {
testLine += ' ' + words[i];
textElement.text(testLine);
var testBox = textElement.node()
.getBoundingClientRect();
if (testBox.width <= width - padding) {
curLine = testLine;
} else {
lines.push(curLine);
curLine = words[i];
testLine = curLine;
}
if (i === words.length - 1) {
lines.push(curLine);
}
}
textElement.text('');
for (i = 0; i < lines.length; i++) {
var tspan = textElement.append('tspan')
.text(lines[i]);
if (i > 0) {
tspan.attr({
'x': 0 + 'px',
'dy': dy + 'px'
})
}
}
},
'help': function() {
console.log('d3.domoStrings is an object of functions that will alter strings with font shrinking and truncating.\n\ntrunc(string, characters, useWordBoundary): The string to modify and the characters you want to truncate to.\nEx: .text(function(d){ return d3.domoStrings.trunc(d.name, 25)\n\ntruncToFit(textElement, width): textElement is the d3 selection of the text element and width is the width that you want the element to fit inside after truncating.\nEx: d3.selectAll("text").each(function(){ return d3.domoStrings.truncToFit(d3.select(this), 45); });\n\nshrinkToFit(textElement, width, height, size): textElement is the d3 selection of the text element, width is the target width to shrink to, height is the target height, size is the starting size of the font.\nEx: Ex: d3.selectAll("text").each(function(){ return d3.domoStrings.shrinkToFit(d3.select(this), 45, 25, 10); });\n\nshrinkToTrunc(textElement, width, size): textElement is the d3 selection of the text element, width is the target wid