UNPKG

lucid-ui

Version:

A UI component library from AppNexus.

233 lines (208 loc) 7.18 kB
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); }