universal y intervals, min limit in case of line chart

This commit is contained in:
pratu16x7 2017-11-14 03:40:15 +05:30
parent ebfd6bd8dc
commit 3de049c451
7 changed files with 526 additions and 409 deletions

View File

@ -671,9 +671,176 @@ function runSVGAnimation(svg_container, elements) {
return anim_svg;
}
// export function calc_intervals() {
// //
// }
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 get_commafied_or_powered_number(number) {}
function get_actual_pretty_num(number, exponent) {
return number;
}
function get_range_intervals(max) {
var min = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
var upper_bound = Math.ceil(max);
var lower_bound = Math.floor(min);
var range = upper_bound - lower_bound;
var no_of_parts = range;
var part_size = 1;
if (range > 5) {
if (range % 2 !== 0) {
upper_bound++;
// Recalc range
range = upper_bound - lower_bound;
}
no_of_parts = range / 2;
part_size = 2;
}
if (range <= 2) {
no_of_parts = 4;
part_size = range / no_of_parts;
}
var intervals = [];
for (var i = 0; i <= no_of_parts; i++) {
intervals.push(lower_bound + part_size * i);
}
return intervals;
}
function get_intervals(max_value) {
var min_value = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
var _normalize = normalize(max_value),
_normalize2 = slicedToArray(_normalize, 2),
normal_max_value = _normalize2[0],
exponent = _normalize2[1];
var normal_min_value = min_value ? min_value / Math.pow(10, exponent) : 0;
// Allow only 7 significant digits
normal_max_value = normal_max_value.toFixed(6);
var intervals = get_range_intervals(normal_max_value, normal_min_value);
intervals = intervals.map(function (value) {
return value * Math.pow(10, exponent);
});
return intervals;
}
function calc_intervals(values) {
var with_minimum = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
//*** Where the magic happens ***
// Calculates best-fit y intervals from given values
// and returns the interval array
var max_value = Math.max.apply(Math, toConsumableArray(values));
var min_value = Math.min.apply(Math, toConsumableArray(values));
var exponent = 0,
intervals = [];
// CASE I: Both non-negative
if (max_value >= 0 && min_value >= 0) {
exponent = normalize(max_value)[1];
if (!with_minimum) {
intervals = get_intervals(max_value);
} else {
intervals = get_intervals(max_value, min_value);
}
}
// CASE II: Only min_value negative
if (max_value > 0 && min_value < 0) {
// `with_minimum` 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
var get_positive_first_intervals = function get_positive_first_intervals(max_value, abs_min_value) {
var intervals = get_intervals(max_value);
var interval_size = intervals[1] - intervals[0];
// Then unshift the negative values
var value = 0;
for (var i = 1; value < abs_min_value; i++) {
value += interval_size;
intervals.unshift(-1 * value);
}
return intervals;
};
var abs_min_value = Math.abs(min_value);
if (max_value >= abs_min_value) {
exponent = normalize(max_value)[1];
intervals = get_positive_first_intervals(max_value, abs_min_value);
} else {
// Mirror: max_value => abs_min_value, then change sign
exponent = normalize(abs_min_value)[1];
var pos_intervals = get_positive_first_intervals(abs_min_value, max_value);
intervals = pos_intervals.map(function (d) {
return d * -1;
});
}
}
// CASE III: Both non-positive
if (max_value <= 0 && min_value <= 0) {
// Mirrored Case I:
// Work with positives, then reverse the sign and array
var pseudo_max_value = Math.abs(min_value);
var pseudo_min_value = Math.abs(max_value);
exponent = normalize(pseudo_max_value)[1];
if (!with_minimum) {
intervals = get_intervals(pseudo_max_value);
} else {
intervals = get_intervals(pseudo_max_value, pseudo_min_value);
}
intervals = intervals.reverse().map(function (d) {
return d * -1;
});
}
intervals = intervals.map(function (value) {
return get_actual_pretty_num(value, exponent);
});
return intervals;
}
function calc_distribution(values, distribution_size) {
// Assume non-negative values,
@ -698,137 +865,6 @@ function get_max_checkpoint(value, distribution) {
}).length;
}
function calc_y_intervals(array) {
//*** Where the magic happens ***
// Calculates best-fit y intervals from given values
// and returns the interval array
// TODO: Fractions
var max_bound = void 0,
min_bound = void 0,
pos_no_of_parts = void 0,
neg_no_of_parts = void 0,
part_size = void 0; // eslint-disable-line no-unused-vars
// Critical values
var max_val = parseInt(Math.max.apply(Math, toConsumableArray(array)));
var min_val = parseInt(Math.min.apply(Math, toConsumableArray(array)));
if (min_val >= 0) {
min_val = 0;
}
var get_params = function get_params(value1, value2) {
var bound1 = void 0,
bound2 = void 0,
no_of_parts_1 = void 0,
no_of_parts_2 = void 0,
interval_size = void 0;
if ((value1 + "").length <= 1) {
bound1 = 10;
no_of_parts_1 = 5;
} else {
var _calc_upper_bound_and = calc_upper_bound_and_no_of_parts(value1);
var _calc_upper_bound_and2 = slicedToArray(_calc_upper_bound_and, 2);
bound1 = _calc_upper_bound_and2[0];
no_of_parts_1 = _calc_upper_bound_and2[1];
}
interval_size = bound1 / no_of_parts_1;
no_of_parts_2 = calc_no_of_parts(value2, interval_size);
bound2 = no_of_parts_2 * interval_size;
return [bound1, bound2, no_of_parts_1, no_of_parts_2, interval_size];
};
var abs_min_val = min_val * -1;
if (abs_min_val <= max_val) {
var _get_params = get_params(max_val, abs_min_val);
// Get the positive region intervals
// then calc negative ones accordingly
var _get_params2 = slicedToArray(_get_params, 5);
min_bound = _get_params2[1];
pos_no_of_parts = _get_params2[2];
neg_no_of_parts = _get_params2[3];
part_size = _get_params2[4];
if (abs_min_val === 0) {
min_bound = 0;neg_no_of_parts = 0;
}
} else {
var _get_params3 = get_params(abs_min_val, max_val);
// Get the negative region here first
var _get_params4 = slicedToArray(_get_params3, 5);
min_bound = _get_params4[0];
neg_no_of_parts = _get_params4[2];
pos_no_of_parts = _get_params4[3];
part_size = _get_params4[4];
}
// Make both region parts even
if (pos_no_of_parts % 2 !== 0 && neg_no_of_parts > 0) pos_no_of_parts++;
if (neg_no_of_parts % 2 !== 0) {
// every increase in no_of_parts entails an increase in corresponding bound
// except here, it happens implicitly after every calc_no_of_parts() call
neg_no_of_parts++;
min_bound += part_size;
}
var no_of_parts = pos_no_of_parts + neg_no_of_parts;
if (no_of_parts > 5) {
no_of_parts /= 2;
part_size *= 2;
pos_no_of_parts /= 2;
}
if (max_val < (pos_no_of_parts - 1) * part_size) {
no_of_parts--;
}
return get_intervals(-1 * min_bound, part_size, no_of_parts);
}
function get_intervals(start, interval_size, count) {
var intervals = [];
for (var i = 0; i <= count; i++) {
intervals.push(start);
start += interval_size;
}
return intervals;
}
function calc_upper_bound_and_no_of_parts(max_val) {
// Given a positive value, calculates a nice-number upper bound
// and a consequent optimal number of parts
var part_size = Math.pow(10, (max_val + "").length - 1);
var no_of_parts = calc_no_of_parts(max_val, part_size);
// Use it to get a nice even upper bound
var upper_bound = part_size * no_of_parts;
return [upper_bound, no_of_parts];
}
function calc_no_of_parts(value, divisor) {
// value should be a positive number, divisor should be greater than 0
// returns an even no of parts
var no_of_parts = Math.ceil(value / divisor);
if (no_of_parts % 2 !== 0) no_of_parts++; // Make it an even number
return no_of_parts;
}
/**
* Returns the value of a number upto 2 decimal places.
* @param {Number} d Any number
@ -1385,7 +1421,7 @@ var AxisChart = function (_BaseChart) {
values = values.concat(this.y_sums);
}
this.y_axis_values = calc_y_intervals(values);
this.y_axis_values = calc_intervals(values, this.type === 'line');
if (!this.y_old_axis_values) {
this.y_old_axis_values = this.y_axis_values.slice();
@ -1398,10 +1434,21 @@ var AxisChart = function (_BaseChart) {
this.multiplier = this.height / value_range;
if (!this.old_multiplier) this.old_multiplier = this.multiplier;
var zero_index = y_pts.indexOf(0);
var interval = y_pts[1] - y_pts[0];
var interval_height = interval * this.multiplier;
var zero_index = void 0;
if (y_pts.indexOf(0) >= 0) {
zero_index = y_pts.indexOf(0);
} else if (y_pts[0] > 0) {
var min = y_pts[0];
zero_index = -1 * min / interval;
} else {
var max = y_pts[y_pts.length - 1];
zero_index = -1 * max / interval + (y_pts.length - 1);
}
if (this.zero_line) this.old_zero_line = this.zero_line;
this.zero_line = this.height - zero_index * interval_height;
if (!this.old_zero_line) this.old_zero_line = this.zero_line;

View File

@ -669,9 +669,176 @@ function runSVGAnimation(svg_container, elements) {
return anim_svg;
}
// export function calc_intervals() {
// //
// }
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 get_commafied_or_powered_number(number) {}
function get_actual_pretty_num(number, exponent) {
return number;
}
function get_range_intervals(max) {
var min = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
var upper_bound = Math.ceil(max);
var lower_bound = Math.floor(min);
var range = upper_bound - lower_bound;
var no_of_parts = range;
var part_size = 1;
if (range > 5) {
if (range % 2 !== 0) {
upper_bound++;
// Recalc range
range = upper_bound - lower_bound;
}
no_of_parts = range / 2;
part_size = 2;
}
if (range <= 2) {
no_of_parts = 4;
part_size = range / no_of_parts;
}
var intervals = [];
for (var i = 0; i <= no_of_parts; i++) {
intervals.push(lower_bound + part_size * i);
}
return intervals;
}
function get_intervals(max_value) {
var min_value = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
var _normalize = normalize(max_value),
_normalize2 = slicedToArray(_normalize, 2),
normal_max_value = _normalize2[0],
exponent = _normalize2[1];
var normal_min_value = min_value ? min_value / Math.pow(10, exponent) : 0;
// Allow only 7 significant digits
normal_max_value = normal_max_value.toFixed(6);
var intervals = get_range_intervals(normal_max_value, normal_min_value);
intervals = intervals.map(function (value) {
return value * Math.pow(10, exponent);
});
return intervals;
}
function calc_intervals(values) {
var with_minimum = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
//*** Where the magic happens ***
// Calculates best-fit y intervals from given values
// and returns the interval array
var max_value = Math.max.apply(Math, toConsumableArray(values));
var min_value = Math.min.apply(Math, toConsumableArray(values));
var exponent = 0,
intervals = [];
// CASE I: Both non-negative
if (max_value >= 0 && min_value >= 0) {
exponent = normalize(max_value)[1];
if (!with_minimum) {
intervals = get_intervals(max_value);
} else {
intervals = get_intervals(max_value, min_value);
}
}
// CASE II: Only min_value negative
if (max_value > 0 && min_value < 0) {
// `with_minimum` 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
var get_positive_first_intervals = function get_positive_first_intervals(max_value, abs_min_value) {
var intervals = get_intervals(max_value);
var interval_size = intervals[1] - intervals[0];
// Then unshift the negative values
var value = 0;
for (var i = 1; value < abs_min_value; i++) {
value += interval_size;
intervals.unshift(-1 * value);
}
return intervals;
};
var abs_min_value = Math.abs(min_value);
if (max_value >= abs_min_value) {
exponent = normalize(max_value)[1];
intervals = get_positive_first_intervals(max_value, abs_min_value);
} else {
// Mirror: max_value => abs_min_value, then change sign
exponent = normalize(abs_min_value)[1];
var pos_intervals = get_positive_first_intervals(abs_min_value, max_value);
intervals = pos_intervals.map(function (d) {
return d * -1;
});
}
}
// CASE III: Both non-positive
if (max_value <= 0 && min_value <= 0) {
// Mirrored Case I:
// Work with positives, then reverse the sign and array
var pseudo_max_value = Math.abs(min_value);
var pseudo_min_value = Math.abs(max_value);
exponent = normalize(pseudo_max_value)[1];
if (!with_minimum) {
intervals = get_intervals(pseudo_max_value);
} else {
intervals = get_intervals(pseudo_max_value, pseudo_min_value);
}
intervals = intervals.reverse().map(function (d) {
return d * -1;
});
}
intervals = intervals.map(function (value) {
return get_actual_pretty_num(value, exponent);
});
return intervals;
}
function calc_distribution(values, distribution_size) {
// Assume non-negative values,
@ -696,137 +863,6 @@ function get_max_checkpoint(value, distribution) {
}).length;
}
function calc_y_intervals(array) {
//*** Where the magic happens ***
// Calculates best-fit y intervals from given values
// and returns the interval array
// TODO: Fractions
var max_bound = void 0,
min_bound = void 0,
pos_no_of_parts = void 0,
neg_no_of_parts = void 0,
part_size = void 0; // eslint-disable-line no-unused-vars
// Critical values
var max_val = parseInt(Math.max.apply(Math, toConsumableArray(array)));
var min_val = parseInt(Math.min.apply(Math, toConsumableArray(array)));
if (min_val >= 0) {
min_val = 0;
}
var get_params = function get_params(value1, value2) {
var bound1 = void 0,
bound2 = void 0,
no_of_parts_1 = void 0,
no_of_parts_2 = void 0,
interval_size = void 0;
if ((value1 + "").length <= 1) {
bound1 = 10;
no_of_parts_1 = 5;
} else {
var _calc_upper_bound_and = calc_upper_bound_and_no_of_parts(value1);
var _calc_upper_bound_and2 = slicedToArray(_calc_upper_bound_and, 2);
bound1 = _calc_upper_bound_and2[0];
no_of_parts_1 = _calc_upper_bound_and2[1];
}
interval_size = bound1 / no_of_parts_1;
no_of_parts_2 = calc_no_of_parts(value2, interval_size);
bound2 = no_of_parts_2 * interval_size;
return [bound1, bound2, no_of_parts_1, no_of_parts_2, interval_size];
};
var abs_min_val = min_val * -1;
if (abs_min_val <= max_val) {
var _get_params = get_params(max_val, abs_min_val);
// Get the positive region intervals
// then calc negative ones accordingly
var _get_params2 = slicedToArray(_get_params, 5);
min_bound = _get_params2[1];
pos_no_of_parts = _get_params2[2];
neg_no_of_parts = _get_params2[3];
part_size = _get_params2[4];
if (abs_min_val === 0) {
min_bound = 0;neg_no_of_parts = 0;
}
} else {
var _get_params3 = get_params(abs_min_val, max_val);
// Get the negative region here first
var _get_params4 = slicedToArray(_get_params3, 5);
min_bound = _get_params4[0];
neg_no_of_parts = _get_params4[2];
pos_no_of_parts = _get_params4[3];
part_size = _get_params4[4];
}
// Make both region parts even
if (pos_no_of_parts % 2 !== 0 && neg_no_of_parts > 0) pos_no_of_parts++;
if (neg_no_of_parts % 2 !== 0) {
// every increase in no_of_parts entails an increase in corresponding bound
// except here, it happens implicitly after every calc_no_of_parts() call
neg_no_of_parts++;
min_bound += part_size;
}
var no_of_parts = pos_no_of_parts + neg_no_of_parts;
if (no_of_parts > 5) {
no_of_parts /= 2;
part_size *= 2;
pos_no_of_parts /= 2;
}
if (max_val < (pos_no_of_parts - 1) * part_size) {
no_of_parts--;
}
return get_intervals(-1 * min_bound, part_size, no_of_parts);
}
function get_intervals(start, interval_size, count) {
var intervals = [];
for (var i = 0; i <= count; i++) {
intervals.push(start);
start += interval_size;
}
return intervals;
}
function calc_upper_bound_and_no_of_parts(max_val) {
// Given a positive value, calculates a nice-number upper bound
// and a consequent optimal number of parts
var part_size = Math.pow(10, (max_val + "").length - 1);
var no_of_parts = calc_no_of_parts(max_val, part_size);
// Use it to get a nice even upper bound
var upper_bound = part_size * no_of_parts;
return [upper_bound, no_of_parts];
}
function calc_no_of_parts(value, divisor) {
// value should be a positive number, divisor should be greater than 0
// returns an even no of parts
var no_of_parts = Math.ceil(value / divisor);
if (no_of_parts % 2 !== 0) no_of_parts++; // Make it an even number
return no_of_parts;
}
/**
* Returns the value of a number upto 2 decimal places.
* @param {Number} d Any number
@ -1383,7 +1419,7 @@ var AxisChart = function (_BaseChart) {
values = values.concat(this.y_sums);
}
this.y_axis_values = calc_y_intervals(values);
this.y_axis_values = calc_intervals(values, this.type === 'line');
if (!this.y_old_axis_values) {
this.y_old_axis_values = this.y_axis_values.slice();
@ -1396,10 +1432,21 @@ var AxisChart = function (_BaseChart) {
this.multiplier = this.height / value_range;
if (!this.old_multiplier) this.old_multiplier = this.multiplier;
var zero_index = y_pts.indexOf(0);
var interval = y_pts[1] - y_pts[0];
var interval_height = interval * this.multiplier;
var zero_index = void 0;
if (y_pts.indexOf(0) >= 0) {
zero_index = y_pts.indexOf(0);
} else if (y_pts[0] > 0) {
var min = y_pts[0];
zero_index = -1 * min / interval;
} else {
var max = y_pts[y_pts.length - 1];
zero_index = -1 * max / interval + (y_pts.length - 1);
}
if (this.zero_line) this.old_zero_line = this.zero_line;
this.zero_line = this.height - zero_index * interval_height;
if (!this.old_zero_line) this.old_zero_line = this.zero_line;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -20,6 +20,8 @@ let line_composite_data = {
datasets: [{
"color": "green",
"values": [36, 46, 45, 32, 27, 31, 30, 36, 39, 49, 0, 0],
// "values": [36, 46, 45, 32, 27, 31, 30, 36, 39, 49, 40, 40],
// "values": [-36, -46, -45, -32, -27, -31, -30, -36, -39, -49, -40, -40],
// "formatted": ["₹ 0.00", "₹ 0.00", "₹ 0.00", "₹ 61,500.00", "₹ 82,936.88", "₹ 24,010.00", "₹ 0.00", "₹ 0.00", "₹ 25,840.00", "₹ 5,08,048.82", "₹ 1,16,820.00", "₹ 0.00"],
}]
};
@ -36,6 +38,8 @@ let more_line_data = {
8: {values: [36, 24, 38, 27, 15, 22, 24, 38, 32, 57, 139, 26]},
9: {values: [37, 36, 32, 33, 12, 34, 52, 45, 58, 57, 64, 35]},
10: {values: [36, 46, 45, 32, 27, 31, 30, 36, 39, 49, 0, 0]}
// 10: {values: [36, 46, 45, 32, 27, 31, 30, 36, 39, 49, 40, 40]}
// 10: {values: [-36, -46, -45, -32, -27, -31, -30, -36, -39, -49, -40, -40]}
};
let c1 = document.querySelector("#chart-composite-1");

View File

@ -1,7 +1,7 @@
import $ from '../utils/dom';
import { UnitRenderer, make_x_line, make_y_line } from '../utils/draw';
import { runSVGAnimation } from '../utils/animate';
import { calc_y_intervals } from '../utils/intervals';
import { calc_intervals } from '../utils/intervals';
import { float_2, arrays_equal, get_string_width } from '../utils/helpers';
import BaseChart from './BaseChart';
@ -56,7 +56,7 @@ export default class AxisChart extends BaseChart {
values = values.concat(this.y_sums);
}
this.y_axis_values = calc_y_intervals(values);
this.y_axis_values = calc_intervals(values, this.type === 'line');
if(!this.y_old_axis_values) {
this.y_old_axis_values = this.y_axis_values.slice();
@ -69,10 +69,21 @@ export default class AxisChart extends BaseChart {
this.multiplier = this.height / value_range;
if(!this.old_multiplier) this.old_multiplier = this.multiplier;
const zero_index = y_pts.indexOf(0);
const interval = y_pts[1] - y_pts[0];
const interval_height = interval * this.multiplier;
let zero_index;
if(y_pts.indexOf(0) >= 0) {
zero_index = y_pts.indexOf(0);
} else if(y_pts[0] > 0) {
let min = y_pts[0];
zero_index = (-1) * min / interval;
} else {
let max = y_pts[y_pts.length - 1];
zero_index = (-1) * max / interval + (y_pts.length - 1);
}
if(this.zero_line) this.old_zero_line = this.zero_line;
this.zero_line = this.height - (zero_index * interval_height);
if(!this.old_zero_line) this.old_zero_line = this.zero_line;

View File

@ -1,36 +1,155 @@
export function divide_if_more_than_five(number) {
return (number < 5) ? number : number / 2;
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];
}
export function calc_whole_parts(value, divisor) {
return Math.ceil(value / divisor);
// function get_commafied_or_powered_number(number) {}
function get_actual_pretty_num(number, exponent) {
return number;
}
export function make_even(number) {
return (number % 2 !== 0) ? ++number : number;
}
function get_range_intervals(max, min=0) {
let upper_bound = Math.ceil(max);
let lower_bound = Math.floor(min);
let range = upper_bound - lower_bound;
export function calc_part_size(value) {
// take care of fractions
return Math.pow(10, ((value+"").length - 1));
}
let no_of_parts = range;
let part_size = 1;
export function calc_upper_bound(value) {
//
}
if(range > 5) {
if(range % 2 !== 0) {
upper_bound++;
// Recalc range
range = upper_bound - lower_bound;
}
no_of_parts = range/2;
part_size = 2;
}
if(range <= 2) {
no_of_parts = 4;
part_size = range/no_of_parts;
}
export function clump_intervals(start, interval_size, count) {
let intervals = [];
for(var i = 0; i <= count; i++){
intervals.push(start);
start += interval_size;
for(var i = 0; i <= no_of_parts; i++){
intervals.push(lower_bound + part_size * i);
}
return intervals;
}
// export function calc_intervals() {
// //
// }
function get_intervals(max_value, min_value=0) {
let [normal_max_value, exponent] = normalize(max_value);
let normal_min_value = min_value ? min_value/Math.pow(10, exponent): 0;
// Allow only 7 significant digits
normal_max_value = normal_max_value.toFixed(6);
let intervals = get_range_intervals(normal_max_value, normal_min_value);
intervals = intervals.map(value => value * Math.pow(10, exponent));
return intervals;
}
export function calc_intervals(values, with_minimum=false) {
//*** Where the magic happens ***
// Calculates best-fit y intervals from given values
// and returns the interval array
let max_value = Math.max(...values);
let min_value = Math.min(...values);
let exponent = 0, intervals = [];
// CASE I: Both non-negative
if(max_value >= 0 && min_value >= 0) {
exponent = normalize(max_value)[1];
if(!with_minimum) {
intervals = get_intervals(max_value);
} else {
intervals = get_intervals(max_value, min_value);
}
}
// CASE II: Only min_value negative
if(max_value > 0 && min_value < 0) {
// `with_minimum` 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
function get_positive_first_intervals(max_value, abs_min_value) {
let intervals = get_intervals(max_value);
let interval_size = intervals[1] - intervals[0];
// Then unshift the negative values
let value = 0;
for(var i = 1; value < abs_min_value; i++) {
value += interval_size;
intervals.unshift((-1) * value)
}
return intervals;
}
let abs_min_value = Math.abs(min_value);
if(max_value >= abs_min_value) {
exponent = normalize(max_value)[1];
intervals = get_positive_first_intervals(max_value, abs_min_value);
} else {
// Mirror: max_value => abs_min_value, then change sign
exponent = normalize(abs_min_value)[1];
let pos_intervals = get_positive_first_intervals(abs_min_value, max_value);
intervals = pos_intervals.map(d => d * (-1));
}
}
// CASE III: Both non-positive
if(max_value <= 0 && min_value <= 0) {
// Mirrored Case I:
// Work with positives, then reverse the sign and array
let pseudo_max_value = Math.abs(min_value);
let pseudo_min_value = Math.abs(max_value);
exponent = normalize(pseudo_max_value)[1];
if(!with_minimum) {
intervals = get_intervals(pseudo_max_value);
} else {
intervals = get_intervals(pseudo_max_value, pseudo_min_value);
}
intervals = intervals.reverse().map(d => d * (-1));
}
intervals = intervals.map(value => get_actual_pretty_num(value, exponent));
return intervals;
}
export function calc_distribution(values, distribution_size) {
// Assume non-negative values,
@ -52,114 +171,3 @@ export function calc_distribution(values, distribution_size) {
export function get_max_checkpoint(value, distribution) {
return distribution.filter(d => d < value).length;
}
export function calc_y_intervals(array) {
//*** Where the magic happens ***
// Calculates best-fit y intervals from given values
// and returns the interval array
// TODO: Fractions
let max_bound, min_bound, pos_no_of_parts, neg_no_of_parts, part_size; // eslint-disable-line no-unused-vars
// Critical values
let max_val = parseInt(Math.max(...array));
let min_val = parseInt(Math.min(...array));
if(min_val >= 0) {
min_val = 0;
}
let get_params = (value1, value2) => {
let bound1, bound2, no_of_parts_1, no_of_parts_2, interval_size;
if((value1+"").length <= 1) {
[bound1, no_of_parts_1] = [10, 5];
} else {
[bound1, no_of_parts_1] = calc_upper_bound_and_no_of_parts(value1);
}
interval_size = bound1 / no_of_parts_1;
no_of_parts_2 = calc_no_of_parts(value2, interval_size);
bound2 = no_of_parts_2 * interval_size;
return [bound1, bound2, no_of_parts_1, no_of_parts_2, interval_size];
};
const abs_min_val = min_val * -1;
if(abs_min_val <= max_val) {
// Get the positive region intervals
// then calc negative ones accordingly
[max_bound, min_bound, pos_no_of_parts, neg_no_of_parts, part_size]
= get_params(max_val, abs_min_val);
if(abs_min_val === 0) {
min_bound = 0; neg_no_of_parts = 0;
}
} else {
// Get the negative region here first
[min_bound, max_bound, neg_no_of_parts, pos_no_of_parts, part_size]
= get_params(abs_min_val, max_val);
}
// Make both region parts even
if(pos_no_of_parts % 2 !== 0 && neg_no_of_parts > 0) pos_no_of_parts++;
if(neg_no_of_parts % 2 !== 0) {
// every increase in no_of_parts entails an increase in corresponding bound
// except here, it happens implicitly after every calc_no_of_parts() call
neg_no_of_parts++;
min_bound += part_size;
}
let no_of_parts = pos_no_of_parts + neg_no_of_parts;
if(no_of_parts > 5) {
no_of_parts /= 2;
part_size *= 2;
pos_no_of_parts /=2;
}
if (max_val < (pos_no_of_parts - 1) * part_size) {
no_of_parts--;
}
return get_intervals(
(-1) * min_bound,
part_size,
no_of_parts
);
}
function get_intervals(start, interval_size, count) {
let intervals = [];
for(var i = 0; i <= count; i++){
intervals.push(start);
start += interval_size;
}
return intervals;
}
function calc_upper_bound_and_no_of_parts(max_val) {
// Given a positive value, calculates a nice-number upper bound
// and a consequent optimal number of parts
const part_size = Math.pow(10, ((max_val+"").length - 1));
const no_of_parts = calc_no_of_parts(max_val, part_size);
// Use it to get a nice even upper bound
const upper_bound = part_size * no_of_parts;
return [upper_bound, no_of_parts];
}
function calc_no_of_parts(value, divisor) {
// value should be a positive number, divisor should be greater than 0
// returns an even no of parts
let no_of_parts = Math.ceil(value / divisor);
if(no_of_parts % 2 !== 0) no_of_parts++; // Make it an even number
return no_of_parts;
}
function get_optimal_no_of_parts(no_of_parts) {
// aka Divide by 2 if too large
return (no_of_parts < 5) ? no_of_parts : no_of_parts / 2;
}