lucid-ui
Version:
A UI component library from AppNexus.
233 lines (208 loc) • 7.18 kB
JavaScript
import _times from "lodash/times";
import _isNil from "lodash/isNil";
import _pick from "lodash/pick";
import _toArray from "lodash/toArray";
import _sum from "lodash/sum";
import _max from "lodash/max";
import _min from "lodash/min";
import _first from "lodash/first";
import _last from "lodash/last";
import _isEmpty from "lodash/isEmpty";
import _get from "lodash/get";
import _reduce from "lodash/reduce";
import _map from "lodash/map";
import _castArray from "lodash/castArray";
import * as d3TimeFormat from 'd3-time-format';
import * as d3Time from 'd3-time';
/**
* stackByFields
*
* D3's `stack` groups each series' data together but we sometimes we want the
* stacked groups to remain grouped as in the original normalized data. This
* function helps achieve that.
*
* @param {object[]} collection - normalized data you want to operate on
* @param {string[]} fields - fields to pluck off for the y data
* @return {array[]} - array of arrays, one for row in the original `collection`
*/
export function stackByFields(collection, fields) {
var fieldsArray = _castArray(fields);
return _map(collection, function (d) {
return _reduce(fieldsArray, function (acc, field) {
var dataPoint = _get(d, field, 0);
if (_isEmpty(acc)) {
return acc.concat([[0, dataPoint]]);
}
var maybeLast = _last(_last(acc));
var last = maybeLast === undefined ? 0 : maybeLast;
return acc.concat([[last, last + dataPoint]]);
}, []);
});
}
/**
* extractFields
*
* This will return the data in a similar format to stackByFields but without
* the stacking.
*
* @param {object[]} collection - normalized data you want to operate on
* @param {string[]} fields - fields to pluck off for the y data
* @return {array[]} - array of arrays, one for each field
*/
export function extractFields(collection, fields) {
var minDomainValue = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
var fieldsArray = _castArray(fields);
return _map(collection, function (d) {
return _map(fieldsArray, function (field) {
return [minDomainValue, _get(d, field, 0)];
});
});
}
/**
* groupByFields
*
* This will return the data in a similar format to d3Shape.stack
* but without the stacking of the data.
*
* @param {object[]} collection - normalized data you want to operate on
* @param {string[]} fields - fields to pluck off for the y data
* @return {array[]} - array of arrays, one for each field
*/
export function groupByFields(collection, fields) {
var fieldsArray = _castArray(fields);
return _map(fieldsArray, function (field) {
return _map(collection, field);
});
}
/**
* byFields
*
* Takes a collection of data and returns an array of all the fields off that
* collection.
*
* @param {object[]} collection
* @param {string[]} fields
* @return {array}
*/
export function byFields(collection, fields) {
var fieldArray = _castArray(fields);
return _reduce(fieldArray, function (acc, field) {
return acc.concat(_map(collection, field));
}, []);
}
/**
* nearest
*
* Divide and conquer algorithm that helps find the nearest element to `value`
* in `nums`
*
* @param {number[]} nums - sorted array of numbers to search through
* @param {number} value - value you're trying to locate the nearest array element for
* @return {number} - the nearest array element to the value
*/
export function nearest(nums, value) {
if (nums.length < 2) {
return _first(nums);
}
if (nums.length === 2) {
return value > (nums[0] + nums[1]) / 2 ? nums[1] : nums[0];
}
var mid = nums.length >>> 1;
return nums[mid] > value ? nearest(nums.slice(0, mid + 1), value) : nearest(nums.slice(mid), value);
}
/**
* minByFields
*
* Returns the minimum element from a collection by a set of fields.
*
* @param {object[]} collection
* @param {string[]} fields
* @return {any}
*/
export function minByFields(collection, fields) {
return _min(byFields(collection, fields));
}
/**
* maxByFields
*
* Returns the maximum element from a collection by a set of fields.
*
* @param {object[]} collection
* @param {string[]} fields
* @return {any}
*/
export function maxByFields(collection, fields) {
return _max(byFields(collection, fields));
}
/**
* maxByFieldsStacked
*
* Returns the max sum of a set of fields from a collection
*
* @param {object[]} collection
* @param {string[]} fields
* @return {any}
*/
export function maxByFieldsStacked(collection, fields) {
var fieldArray = _castArray(fields);
var sums = _reduce(collection, function (acc, item) {
return acc.concat(_sum(_toArray(_pick(item, fieldArray))));
}, []);
return _max(sums);
}
/**
* discreteTicks
*
* Returns `count` evenly spaced, representative values from the `array`.
*
* @param {array} array
* @param {number} size - should be greater than 1
* @return {array}
*/
export function discreteTicks(array) {
var count = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
if (!array || _isNil(count) || array.length <= count) {
return array;
}
var step = (array.length - 1) / Math.max(1, count - 1);
return _reduce(_times(count), function (acc, n) {
return acc.concat(array[Math.round(n * step)]);
}, []);
}
/**
* transformFromCenter
*
* Scaling paths from their center is tricky. This function
* helps do that be generating a translate/scale transform
* string with the correct numbers.
*
* @param {number} x - the x data point where you want the path to be centered at
* @param {number} y - the y data point where you want the path to be centered at
* @param {number} xCenter - the x coordinate of the center of the path you're trying to transform
* @param {number} yCenter - the x coordinate of the center of the path you're trying to transform
* @param {number} scale - number to scale to, 2 would be 2x bigger
* @return {string} - transform string
*/
export function transformFromCenter(x, y, xCenter, yCenter, scale) {
return "translate(".concat((1 - scale) * xCenter + (x - xCenter), ", ").concat((1 - scale) * yCenter + (y - yCenter), ") scale(").concat(scale, ")");
}
var FORMAT_MILLISECOND = d3TimeFormat.timeFormat('.%L');
var FORMAT_SECOND = d3TimeFormat.timeFormat(':%S');
var FORMAT_MINUTE = d3TimeFormat.timeFormat('%I:%M');
var FORMAT_HOUR = d3TimeFormat.timeFormat('%I %p');
var FORMAT_DAY = d3TimeFormat.timeFormat('%a %d');
var FORMAT_WEEK = d3TimeFormat.timeFormat('%b %d');
var FORMAT_MONTH = d3TimeFormat.timeFormat('%b');
var FORMAT_YEAR = d3TimeFormat.timeFormat('%Y');
/**
* formatDate
*
* This function was written to be used for tick formatting with d3 time
* scales.
*
* @param {date} date - input date
* @return {string} - formatted date
*/
export function formatDate(date) {
return (d3Time.timeSecond(date) < date ? FORMAT_MILLISECOND : d3Time.timeMinute(date) < date ? FORMAT_SECOND : d3Time.timeHour(date) < date ? FORMAT_MINUTE : d3Time.timeDay(date) < date ? FORMAT_HOUR : d3Time.timeMonth(date) < date ? d3Time.timeWeek(date) < date ? FORMAT_DAY : FORMAT_WEEK : d3Time.timeYear(date) < date ? FORMAT_MONTH : FORMAT_YEAR)(date);
}