@deepsource/charts
Version:
<div align="center"> <img src="https://github.com/frappe/design/blob/master/logos/logo-2019/frappe-charts-logo.png" height="128"> <a href="https://frappe.github.io/charts"> <h2>Frappe Charts</h2> </a> </div>
248 lines (195 loc) • 6.28 kB
JavaScript
import { floatTwo } from './helpers';
function normalize(x) {
// Calculates mantissa and exponent of a number
// Returns normalized number and exponent
// https://stackoverflow.com/q/9383593/6495043
if (x === 0) {
return [0, 0];
}
if (isNaN(x)) {
return { mantissa: -6755399441055744, exponent: 972 };
}
var sig = x > 0 ? 1 : -1;
if (!isFinite(x)) {
return { mantissa: sig * 4503599627370496, exponent: 972 };
}
x = Math.abs(x);
var exp = Math.floor(Math.log10(x));
var man = x / Math.pow(10, exp);
return [sig * man, exp];
}
function getChartRangeIntervals(max, min = 0) {
let upperBound = Math.ceil(max);
let lowerBound = Math.floor(min);
let range = upperBound - lowerBound;
let noOfParts = range;
let partSize = 1;
// To avoid too many partitions
if (range > 5) {
if (range % 2 !== 0) {
upperBound++;
// Recalc range
range = upperBound - lowerBound;
}
noOfParts = range / 2;
partSize = 2;
}
// Special case: 1 and 2
if (range <= 2) {
noOfParts = 4;
partSize = range / noOfParts;
}
// Special case: 0
if (range === 0) {
noOfParts = 5;
partSize = 1;
}
let intervals = [];
for (var i = 0; i <= noOfParts; i++) {
intervals.push(lowerBound + partSize * i);
}
return intervals;
}
function getChartIntervals(maxValue, minValue = 0) {
let [normalMaxValue, exponent] = normalize(maxValue);
let normalMinValue = minValue ? minValue / Math.pow(10, exponent) : 0;
// Allow only 7 significant digits
normalMaxValue = normalMaxValue.toFixed(6);
let intervals = getChartRangeIntervals(normalMaxValue, normalMinValue);
intervals = intervals.map(value => value * Math.pow(10, exponent));
return intervals;
}
export function calcChartIntervals(values, withMinimum = false, range = {}) {
//*** Where the magic happens ***
// Calculates best-fit y intervals from given values
// and returns the interval array
let maxValue = Math.max(...values);
let minValue = Math.min(...values);
if (range.max !== undefined) {
maxValue = maxValue > range.max ? maxValue : range.max;
}
if (range.min !== undefined) {
minValue = minValue < range.min ? minValue : range.min;
}
// Exponent to be used for pretty print
let exponent = 0, intervals = []; // eslint-disable-line no-unused-vars
function getPositiveFirstIntervals(maxValue, absMinValue) {
let intervals = getChartIntervals(maxValue);
let intervalSize = intervals[1] - intervals[0];
// Then unshift the negative values
let value = 0;
for (var i = 1; value < absMinValue; i++) {
value += intervalSize;
intervals.unshift((-1) * value);
}
return intervals;
}
// CASE I: Both non-negative
if (maxValue >= 0 && minValue >= 0) {
exponent = normalize(maxValue)[1];
if (!withMinimum) {
intervals = getChartIntervals(maxValue);
} else {
intervals = getChartIntervals(maxValue, minValue);
}
}
// CASE II: Only minValue negative
else if (maxValue > 0 && minValue < 0) {
// `withMinimum` irrelevant in this case,
// We'll be handling both sides of zero separately
// (both starting from zero)
// Because ceil() and floor() behave differently
// in those two regions
let absMinValue = Math.abs(minValue);
if (maxValue >= absMinValue) {
exponent = normalize(maxValue)[1];
intervals = getPositiveFirstIntervals(maxValue, absMinValue);
} else {
// Mirror: maxValue => absMinValue, then change sign
exponent = normalize(absMinValue)[1];
let posIntervals = getPositiveFirstIntervals(absMinValue, maxValue);
intervals = posIntervals.reverse().map(d => d * (-1));
}
}
// CASE III: Both non-positive
else if (maxValue <= 0 && minValue <= 0) {
// Mirrored Case I:
// Work with positives, then reverse the sign and array
let pseudoMaxValue = Math.abs(minValue);
let pseudoMinValue = Math.abs(maxValue);
exponent = normalize(pseudoMaxValue)[1];
if (!withMinimum) {
intervals = getChartIntervals(pseudoMaxValue);
} else {
intervals = getChartIntervals(pseudoMaxValue, pseudoMinValue);
}
intervals = intervals.reverse().map(d => d * (-1));
}
return intervals;
}
export function getZeroIndex(yPts) {
let zeroIndex;
let interval = getIntervalSize(yPts);
if (yPts.indexOf(0) >= 0) {
// the range has a given zero
// zero-line on the chart
zeroIndex = yPts.indexOf(0);
} else if (yPts[0] > 0) {
// Minimum value is positive
// zero-line is off the chart: below
let min = yPts[0];
zeroIndex = (-1) * min / interval;
} else {
// Maximum value is negative
// zero-line is off the chart: above
let max = yPts[yPts.length - 1];
zeroIndex = (-1) * max / interval + (yPts.length - 1);
}
return zeroIndex;
}
export function getRealIntervals(max, noOfIntervals, min = 0, asc = 1) {
let range = max - min;
let part = range * 1.0 / noOfIntervals;
let intervals = [];
for (var i = 0; i <= noOfIntervals; i++) {
intervals.push(min + part * i);
}
return asc ? intervals : intervals.reverse();
}
export function getIntervalSize(orderedArray) {
return orderedArray[1] - orderedArray[0];
}
export function getValueRange(orderedArray) {
return orderedArray[orderedArray.length - 1] - orderedArray[0];
}
export function scale(val, yAxis) {
return floatTwo(yAxis.zeroLine - val * yAxis.scaleMultiplier);
}
export function isInRange(val, min, max) {
return val > min && val < max;
}
export function isInRange2D(coord, minCoord, maxCoord) {
return isInRange(coord[0], minCoord[0], maxCoord[0])
&& isInRange(coord[1], minCoord[1], maxCoord[1]);
}
export function getClosestInArray(goal, arr, index = false) {
let closest = arr.reduce(function (prev, curr) {
return (Math.abs(curr - goal) < Math.abs(prev - goal) ? curr : prev);
}, []);
return index ? arr.indexOf(closest) : closest;
}
export function calcDistribution(values, distributionSize) {
// Assume non-negative values,
// implying distribution minimum at zero
let dataMaxValue = Math.max(...values);
let distributionStep = 1 / (distributionSize - 1);
let distribution = [];
for (var i = 0; i < distributionSize; i++) {
let checkpoint = dataMaxValue * (distributionStep * i);
distribution.push(checkpoint);
}
return distribution;
}
export function getMaxCheckpoint(value, distribution) {
return distribution.filter(d => d < value).length;
}