commit
dadc1f8384
1143
dist/frappe-charts.min.cjs.js
vendored
1143
dist/frappe-charts.min.cjs.js
vendored
File diff suppressed because it is too large
Load Diff
1143
dist/frappe-charts.min.esm.js
vendored
1143
dist/frappe-charts.min.esm.js
vendored
File diff suppressed because it is too large
Load Diff
2
dist/frappe-charts.min.iife.js
vendored
2
dist/frappe-charts.min.iife.js
vendored
File diff suppressed because one or more lines are too long
2
docs/assets/js/frappe-charts.min.js
vendored
2
docs/assets/js/frappe-charts.min.js
vendored
File diff suppressed because one or more lines are too long
@ -1,5 +1,8 @@
|
||||
import $ from '../helpers/dom';
|
||||
import { float_2, arrays_equal } from '../helpers/utils';
|
||||
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 { float_2, arrays_equal, get_string_width } from '../utils/helpers';
|
||||
import BaseChart from './BaseChart';
|
||||
|
||||
export default class AxisChart extends BaseChart {
|
||||
@ -15,10 +18,9 @@ export default class AxisChart extends BaseChart {
|
||||
this.get_y_tooltip = this.format_lambdas.y_tooltip;
|
||||
this.get_x_tooltip = this.format_lambdas.x_tooltip;
|
||||
|
||||
this.colors = ['green', 'blue', 'violet', 'red', 'orange',
|
||||
'yellow', 'light-blue', 'light-green', 'purple', 'magenta'];
|
||||
|
||||
this.zero_line = this.height;
|
||||
|
||||
this.old_values = {};
|
||||
}
|
||||
|
||||
setup_values() {
|
||||
@ -54,7 +56,7 @@ export default class AxisChart extends BaseChart {
|
||||
values = values.concat(this.y_sums);
|
||||
}
|
||||
|
||||
this.y_axis_values = this.get_y_axis_points(values);
|
||||
this.y_axis_values = calc_y_intervals(values);
|
||||
|
||||
if(!this.y_old_axis_values) {
|
||||
this.y_old_axis_values = this.y_axis_values.slice();
|
||||
@ -113,6 +115,7 @@ export default class AxisChart extends BaseChart {
|
||||
|
||||
// make VERTICAL lines for x values
|
||||
make_x_axis(animate=false) {
|
||||
let char_width = 8;
|
||||
let start_at, height, text_start_at, axis_line_class = '';
|
||||
if(this.x_axis_mode === 'span') { // long spanning lines
|
||||
start_at = -7;
|
||||
@ -137,7 +140,7 @@ export default class AxisChart extends BaseChart {
|
||||
|
||||
this.x_axis_group.textContent = '';
|
||||
this.x.map((point, i) => {
|
||||
let space_taken = this.get_strwidth(point) + 2;
|
||||
let space_taken = get_string_width(point, char_width) + 2;
|
||||
if(space_taken > allowed_space) {
|
||||
if(this.is_series) {
|
||||
// Skip some axis lines if X axis is a series
|
||||
@ -153,7 +156,7 @@ export default class AxisChart extends BaseChart {
|
||||
}
|
||||
}
|
||||
this.x_axis_group.appendChild(
|
||||
this.make_x_line(
|
||||
make_x_line(
|
||||
height,
|
||||
text_start_at,
|
||||
point,
|
||||
@ -178,7 +181,7 @@ export default class AxisChart extends BaseChart {
|
||||
this.y_axis_group.textContent = '';
|
||||
this.y_axis_values.map((value, i) => {
|
||||
this.y_axis_group.appendChild(
|
||||
this.make_y_line(
|
||||
make_y_line(
|
||||
start_at,
|
||||
width,
|
||||
text_end_at,
|
||||
@ -277,8 +280,10 @@ export default class AxisChart extends BaseChart {
|
||||
units_group.textContent = '';
|
||||
units_array.length = 0;
|
||||
|
||||
let unit_renderer = new UnitRenderer(this.height, this.zero_line, this.avg_unit_width);
|
||||
|
||||
y_values.map((y, i) => {
|
||||
let data_unit = this.draw[unit.type](
|
||||
let data_unit = unit_renderer['draw_' + unit.type](
|
||||
x_values[i],
|
||||
y,
|
||||
unit.args,
|
||||
@ -300,7 +305,7 @@ export default class AxisChart extends BaseChart {
|
||||
this.specific_y_group.textContent = '';
|
||||
this.specific_values.map(d => {
|
||||
this.specific_y_group.appendChild(
|
||||
this.make_y_line(
|
||||
make_y_line(
|
||||
0,
|
||||
this.width,
|
||||
this.width + 5,
|
||||
@ -504,7 +509,7 @@ export default class AxisChart extends BaseChart {
|
||||
}
|
||||
|
||||
run_animation() {
|
||||
let anim_svg = $.runSVGAnimation(this.svg, this.elements_to_animate);
|
||||
let anim_svg = runSVGAnimation(this.svg, this.elements_to_animate);
|
||||
|
||||
if(this.svg.parentNode == this.chart_wrapper) {
|
||||
this.chart_wrapper.removeChild(this.svg);
|
||||
@ -567,14 +572,16 @@ export default class AxisChart extends BaseChart {
|
||||
|
||||
animate_units(d, index, old_x, old_y, new_x, new_y) {
|
||||
let type = this.unit_args.type;
|
||||
let unit_renderer = new UnitRenderer(this.height, this.zero_line, this.avg_unit_width);
|
||||
|
||||
d.svg_units.map((unit, i) => {
|
||||
if(new_x[i] === undefined || new_y[i] === undefined) return;
|
||||
this.elements_to_animate.push(this.animate[type](
|
||||
this.elements_to_animate.push(unit_renderer['animate_' + type](
|
||||
{unit:unit, array:d.svg_units, index: i}, // unit, with info to replace where it came from in the data
|
||||
new_x[i],
|
||||
new_y[i],
|
||||
index
|
||||
index,
|
||||
this.y.length
|
||||
));
|
||||
});
|
||||
}
|
||||
@ -633,7 +640,7 @@ export default class AxisChart extends BaseChart {
|
||||
if(typeof new_pos === 'string') {
|
||||
new_pos = parseInt(new_pos.substring(0, new_pos.length-1));
|
||||
}
|
||||
const x_line = this.make_x_line(
|
||||
const x_line = make_x_line(
|
||||
height,
|
||||
text_start_at,
|
||||
value, // new value
|
||||
@ -742,66 +749,6 @@ export default class AxisChart extends BaseChart {
|
||||
}
|
||||
}
|
||||
|
||||
make_x_line(height, text_start_at, point, label_class, axis_line_class, x_pos) {
|
||||
let line = $.createSVG('line', {
|
||||
x1: 0,
|
||||
x2: 0,
|
||||
y1: 0,
|
||||
y2: height
|
||||
});
|
||||
|
||||
let text = $.createSVG('text', {
|
||||
className: label_class,
|
||||
x: 0,
|
||||
y: text_start_at,
|
||||
dy: '.71em',
|
||||
innerHTML: point
|
||||
});
|
||||
|
||||
let x_level = $.createSVG('g', {
|
||||
className: `tick ${axis_line_class}`,
|
||||
transform: `translate(${ x_pos }, 0)`
|
||||
});
|
||||
|
||||
x_level.appendChild(line);
|
||||
x_level.appendChild(text);
|
||||
|
||||
return x_level;
|
||||
}
|
||||
|
||||
make_y_line(start_at, width, text_end_at, point, label_class, axis_line_class, y_pos, darker=false, line_type="") {
|
||||
let line = $.createSVG('line', {
|
||||
className: line_type === "dashed" ? "dashed": "",
|
||||
x1: start_at,
|
||||
x2: width,
|
||||
y1: 0,
|
||||
y2: 0
|
||||
});
|
||||
|
||||
let text = $.createSVG('text', {
|
||||
className: label_class,
|
||||
x: text_end_at,
|
||||
y: 0,
|
||||
dy: '.32em',
|
||||
innerHTML: point+""
|
||||
});
|
||||
|
||||
let y_level = $.createSVG('g', {
|
||||
className: `tick ${axis_line_class}`,
|
||||
transform: `translate(0, ${y_pos})`,
|
||||
'stroke-opacity': 1
|
||||
});
|
||||
|
||||
if(darker) {
|
||||
line.style.stroke = "rgba(27, 31, 35, 0.6)";
|
||||
}
|
||||
|
||||
y_level.appendChild(line);
|
||||
y_level.appendChild(text);
|
||||
|
||||
return y_level;
|
||||
}
|
||||
|
||||
add_and_animate_y_line(value, old_pos, new_pos, i, group, type, specific=false) {
|
||||
let filler = false;
|
||||
if(typeof new_pos === 'string') {
|
||||
@ -819,7 +766,7 @@ export default class AxisChart extends BaseChart {
|
||||
let [width, text_end_at, axis_line_class, start_at] = this.get_y_axis_line_props(specific);
|
||||
let axis_label_class = !specific ? 'y-value-text' : 'specific-value';
|
||||
value = !specific ? value : (value+"").toUpperCase();
|
||||
const y_line = this.make_y_line(
|
||||
const y_line = make_y_line(
|
||||
start_at,
|
||||
width,
|
||||
text_end_at,
|
||||
@ -843,117 +790,6 @@ export default class AxisChart extends BaseChart {
|
||||
]);
|
||||
}
|
||||
|
||||
get_y_axis_points(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] = this.calc_upper_bound_and_no_of_parts(value1);
|
||||
}
|
||||
|
||||
interval_size = bound1 / no_of_parts_1;
|
||||
no_of_parts_2 = this.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 this.get_intervals(
|
||||
(-1) * min_bound,
|
||||
part_size,
|
||||
no_of_parts
|
||||
);
|
||||
}
|
||||
|
||||
get_intervals(start, interval_size, count) {
|
||||
let intervals = [];
|
||||
for(var i = 0; i <= count; i++){
|
||||
intervals.push(start);
|
||||
start += interval_size;
|
||||
}
|
||||
return intervals;
|
||||
}
|
||||
|
||||
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 = this.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];
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
set_avg_unit_width_and_x_offset() {
|
||||
// Set the ... you get it
|
||||
this.avg_unit_width = this.width/(this.x.length - 1);
|
||||
@ -985,78 +821,4 @@ export default class AxisChart extends BaseChart {
|
||||
// this.chart_wrapper.removeChild(this.tip.container);
|
||||
// this.make_tooltip();
|
||||
}
|
||||
|
||||
get_bar_height_and_y_attr(y_top) {
|
||||
let height, y;
|
||||
if (y_top <= this.zero_line) {
|
||||
height = this.zero_line - y_top;
|
||||
y = y_top;
|
||||
|
||||
// In case of invisible bars
|
||||
if(height === 0) {
|
||||
height = this.height * 0.01;
|
||||
y -= height;
|
||||
}
|
||||
} else {
|
||||
height = y_top - this.zero_line;
|
||||
y = this.zero_line;
|
||||
|
||||
// In case of invisible bars
|
||||
if(height === 0) {
|
||||
height = this.height * 0.01;
|
||||
}
|
||||
}
|
||||
|
||||
return [height, y];
|
||||
}
|
||||
|
||||
setup_utils() {
|
||||
this.draw = {
|
||||
'bar': (x, y_top, args, color, index, dataset_index, no_of_datasets) => {
|
||||
let total_width = this.avg_unit_width - args.space_width;
|
||||
let start_x = x - total_width/2;
|
||||
|
||||
let width = total_width / no_of_datasets;
|
||||
let current_x = start_x + width * dataset_index;
|
||||
|
||||
let [height, y] = this.get_bar_height_and_y_attr(y_top);
|
||||
|
||||
return $.createSVG('rect', {
|
||||
className: `bar mini fill ${color}`,
|
||||
'data-point-index': index,
|
||||
x: current_x,
|
||||
y: y,
|
||||
width: width,
|
||||
height: height
|
||||
});
|
||||
|
||||
},
|
||||
'dot': (x, y, args, color, index) => {
|
||||
return $.createSVG('circle', {
|
||||
className: `fill ${color}`,
|
||||
'data-point-index': index,
|
||||
cx: x,
|
||||
cy: y,
|
||||
r: args.radius
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
this.animate = {
|
||||
'bar': (bar_obj, x, y_top, index) => {
|
||||
let start = x - this.avg_unit_width/4;
|
||||
let width = (this.avg_unit_width/2)/this.y.length;
|
||||
let [height, y] = this.get_bar_height_and_y_attr(y_top);
|
||||
|
||||
x = start + (width * index);
|
||||
|
||||
return [bar_obj, {width: width, height: height, x: x, y: y}, 350, "easein"];
|
||||
// bar.animate({height: args.new_height, y: y_top}, 350, mina.easein);
|
||||
},
|
||||
'dot': (dot_obj, x, y_top) => {
|
||||
return [dot_obj, {cx: x, cy: y_top}, 350, "easein"];
|
||||
// dot.animate({cy: y_top}, 350, mina.easein);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,15 +1,16 @@
|
||||
import SvgTip from '../objects/SvgTip';
|
||||
import $ from '../helpers/dom';
|
||||
import $ from '../utils/dom';
|
||||
import { get_string_width } from '../utils/helpers';
|
||||
import Chart from '../charts';
|
||||
|
||||
export default class BaseChart {
|
||||
constructor({
|
||||
height = 240,
|
||||
|
||||
title = '', subtitle = '',
|
||||
|
||||
title = '',
|
||||
subtitle = '',
|
||||
colors = [],
|
||||
format_lambdas = {},
|
||||
|
||||
summary = [],
|
||||
|
||||
is_navigable = 0,
|
||||
@ -38,6 +39,12 @@ export default class BaseChart {
|
||||
}
|
||||
this.has_legend = has_legend;
|
||||
|
||||
this.colors = colors;
|
||||
if(!this.colors || (this.data.labels && this.colors.length < this.data.labels.length)) {
|
||||
this.colors = ['light-blue', 'blue', 'violet', 'red', 'orange',
|
||||
'yellow', 'green', 'light-green', 'purple', 'magenta'];
|
||||
}
|
||||
|
||||
this.chart_types = ['line', 'scatter', 'bar', 'percentage', 'heatmap', 'pie'];
|
||||
|
||||
this.set_margins(height);
|
||||
@ -122,9 +129,11 @@ export default class BaseChart {
|
||||
|
||||
set_width() {
|
||||
let special_values_width = 0;
|
||||
let char_width = 8;
|
||||
this.specific_values.map(val => {
|
||||
if(this.get_strwidth(val.title) > special_values_width) {
|
||||
special_values_width = this.get_strwidth(val.title) - 40;
|
||||
let str_width = get_string_width((val.title + ""), char_width);
|
||||
if(str_width > special_values_width) {
|
||||
special_values_width = str_width - 40;
|
||||
}
|
||||
});
|
||||
this.base_width = this.parent.offsetWidth - special_values_width;
|
||||
@ -256,11 +265,6 @@ export default class BaseChart {
|
||||
$.fire(this.parent, "data-select", this.get_data_point());
|
||||
}
|
||||
|
||||
// Helpers
|
||||
get_strwidth(string) {
|
||||
return (string+"").length * 8;
|
||||
}
|
||||
|
||||
// Objects
|
||||
setup_utils() { }
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import BaseChart from './BaseChart';
|
||||
import $ from '../helpers/dom';
|
||||
import $ from '../utils/dom';
|
||||
import { add_days, get_dd_mm_yyyy, get_weeks_between } from '../utils/date-utils';
|
||||
|
||||
export default class Heatmap extends BaseChart {
|
||||
constructor({
|
||||
@ -21,7 +22,7 @@ export default class Heatmap extends BaseChart {
|
||||
this.count_label = count_label;
|
||||
|
||||
let today = new Date();
|
||||
this.start = start || this.add_days(today, 365);
|
||||
this.start = start || add_days(today, 365);
|
||||
|
||||
this.legend_colors = ['#ebedf0', '#c6e48b', '#7bc96f', '#239a3b', '#196127'];
|
||||
|
||||
@ -39,12 +40,12 @@ export default class Heatmap extends BaseChart {
|
||||
this.first_week_start = new Date(this.start.toDateString());
|
||||
this.last_week_start = new Date(this.today.toDateString());
|
||||
if(this.first_week_start.getDay() !== 7) {
|
||||
this.add_days(this.first_week_start, (-1) * this.first_week_start.getDay());
|
||||
add_days(this.first_week_start, (-1) * this.first_week_start.getDay());
|
||||
}
|
||||
if(this.last_week_start.getDay() !== 7) {
|
||||
this.add_days(this.last_week_start, (-1) * this.last_week_start.getDay());
|
||||
add_days(this.last_week_start, (-1) * this.last_week_start.getDay());
|
||||
}
|
||||
this.no_of_cols = this.get_weeks_between(this.first_week_start + '', this.last_week_start + '') + 1;
|
||||
this.no_of_cols = get_weeks_between(this.first_week_start + '', this.last_week_start + '') + 1;
|
||||
}
|
||||
|
||||
set_width() {
|
||||
@ -101,7 +102,7 @@ export default class Heatmap extends BaseChart {
|
||||
this.months.push(this.current_month + '');
|
||||
this.month_weeks[this.current_month] = 1;
|
||||
}
|
||||
this.add_days(current_week_sunday, 7);
|
||||
add_days(current_week_sunday, 7);
|
||||
}
|
||||
this.render_month_labels();
|
||||
}
|
||||
@ -148,13 +149,13 @@ export default class Heatmap extends BaseChart {
|
||||
width: square_side,
|
||||
height: square_side,
|
||||
fill: this.legend_colors[color_index],
|
||||
'data-date': this.get_dd_mm_yyyy(current_date),
|
||||
'data-date': get_dd_mm_yyyy(current_date),
|
||||
'data-value': data_value,
|
||||
'data-day': current_date.getDay()
|
||||
});
|
||||
|
||||
let next_date = new Date(current_date);
|
||||
this.add_days(next_date, 1);
|
||||
add_days(next_date, 1);
|
||||
if(next_date.getTime() > today_time) break;
|
||||
|
||||
|
||||
@ -270,39 +271,4 @@ export default class Heatmap extends BaseChart {
|
||||
return d <= value;
|
||||
}).length - 1;
|
||||
}
|
||||
|
||||
// TODO: date utils, move these out
|
||||
|
||||
// https://stackoverflow.com/a/11252167/6495043
|
||||
treat_as_utc(date_str) {
|
||||
let result = new Date(date_str);
|
||||
result.setMinutes(result.getMinutes() - result.getTimezoneOffset());
|
||||
return result;
|
||||
}
|
||||
|
||||
get_dd_mm_yyyy(date) {
|
||||
let dd = date.getDate();
|
||||
let mm = date.getMonth() + 1; // getMonth() is zero-based
|
||||
return [
|
||||
(dd>9 ? '' : '0') + dd,
|
||||
(mm>9 ? '' : '0') + mm,
|
||||
date.getFullYear()
|
||||
].join('-');
|
||||
}
|
||||
|
||||
get_weeks_between(start_date_str, end_date_str) {
|
||||
return Math.ceil(this.get_days_between(start_date_str, end_date_str) / 7);
|
||||
}
|
||||
|
||||
get_days_between(start_date_str, end_date_str) {
|
||||
let milliseconds_per_day = 24 * 60 * 60 * 1000;
|
||||
return (this.treat_as_utc(end_date_str) - this.treat_as_utc(start_date_str)) / milliseconds_per_day;
|
||||
}
|
||||
|
||||
// mutates
|
||||
add_days(date, number_of_days) {
|
||||
date.setDate(date.getDate() + number_of_days);
|
||||
}
|
||||
|
||||
get_month_name() {}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import AxisChart from './AxisChart';
|
||||
import $ from '../helpers/dom';
|
||||
import $ from '../utils/dom';
|
||||
|
||||
export default class LineChart extends AxisChart {
|
||||
constructor(args) {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import BaseChart from './BaseChart';
|
||||
import $ from '../helpers/dom';
|
||||
import $ from '../utils/dom';
|
||||
|
||||
export default class PercentageChart extends BaseChart {
|
||||
constructor(args) {
|
||||
@ -13,13 +13,6 @@ export default class PercentageChart extends BaseChart {
|
||||
this.max_slices = 10;
|
||||
this.max_legend_points = 6;
|
||||
|
||||
this.colors = args.colors;
|
||||
|
||||
if(!this.colors || this.colors.length < this.data.labels.length) {
|
||||
this.colors = ['light-blue', 'blue', 'violet', 'red', 'orange',
|
||||
'yellow', 'green', 'light-green', 'purple', 'magenta'];
|
||||
}
|
||||
|
||||
this.setup();
|
||||
}
|
||||
|
||||
@ -86,8 +79,6 @@ export default class PercentageChart extends BaseChart {
|
||||
this.legend_totals = this.slice_totals.slice(0, this.max_legend_points);
|
||||
}
|
||||
|
||||
setup_utils() { }
|
||||
|
||||
make_graph_components() {
|
||||
this.grand_total = this.slice_totals.reduce((a, b) => a + b, 0);
|
||||
this.slices = [];
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import BaseChart from './BaseChart';
|
||||
import $ from '../helpers/dom';
|
||||
import { lightenDarkenColor } from '../helpers/utils';
|
||||
import $ from '../utils/dom';
|
||||
import { lighten_darken_color } from '../utils/colors';
|
||||
import { runSVGAnimation, transform } from '../utils/animate';
|
||||
const ANGLE_RATIO = Math.PI / 180;
|
||||
const FULL_ANGLE = 360;
|
||||
|
||||
@ -65,7 +66,6 @@ export default class PieChart extends BaseChart {
|
||||
this.legend_totals = this.slice_totals.slice(0, this.max_legend_points);
|
||||
}
|
||||
|
||||
setup_utils() { }
|
||||
static getPositionByAngle(angle,radius){
|
||||
return {
|
||||
x:Math.sin(angle * ANGLE_RATIO) * radius,
|
||||
@ -135,7 +135,7 @@ export default class PieChart extends BaseChart {
|
||||
// if(this.isAnimate) return ;
|
||||
// this.isAnimate = true;
|
||||
if(!this.elements_to_animate || this.elements_to_animate.length === 0) return;
|
||||
let anim_svg = $.runSVGAnimation(this.svg, this.elements_to_animate);
|
||||
let anim_svg = runSVGAnimation(this.svg, this.elements_to_animate);
|
||||
|
||||
if(this.svg.parentNode == this.chart_wrapper) {
|
||||
this.chart_wrapper.removeChild(this.svg);
|
||||
@ -161,8 +161,8 @@ export default class PieChart extends BaseChart {
|
||||
hoverSlice(path,i,flag,e){
|
||||
if(!path) return;
|
||||
if(flag){
|
||||
$.transform(path,this.calTranslateByAngle(this.slicesProperties[i]));
|
||||
path.setAttribute('fill',lightenDarkenColor(this.colors[i],50));
|
||||
transform(path,this.calTranslateByAngle(this.slicesProperties[i]));
|
||||
path.setAttribute('fill',lighten_darken_color(this.colors[i],50));
|
||||
let g_off = $.offset(this.svg);
|
||||
let x = e.pageX - g_off.left + 10;
|
||||
let y = e.pageY - g_off.top - 10;
|
||||
@ -172,7 +172,7 @@ export default class PieChart extends BaseChart {
|
||||
this.tip.set_values(x, y, title, percent + "%");
|
||||
this.tip.show_tip();
|
||||
}else{
|
||||
$.transform(path,'translate3d(0,0,0)');
|
||||
transform(path,'translate3d(0,0,0)');
|
||||
this.tip.hide_tip();
|
||||
path.setAttribute('fill',this.colors[i]);
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import $ from '../helpers/dom';
|
||||
import $ from '../utils/dom';
|
||||
|
||||
export default class SvgTip {
|
||||
constructor({
|
||||
|
||||
101
src/scripts/utils/animate.js
Normal file
101
src/scripts/utils/animate.js
Normal file
@ -0,0 +1,101 @@
|
||||
// Leveraging SMIL Animations
|
||||
|
||||
const EASING = {
|
||||
ease: "0.25 0.1 0.25 1",
|
||||
linear: "0 0 1 1",
|
||||
// easein: "0.42 0 1 1",
|
||||
easein: "0.1 0.8 0.2 1",
|
||||
easeout: "0 0 0.58 1",
|
||||
easeinout: "0.42 0 0.58 1"
|
||||
};
|
||||
|
||||
function animateSVG(element, props, dur, easing_type="linear", type=undefined, old_values={}) {
|
||||
|
||||
let anim_element = element.cloneNode(true);
|
||||
let new_element = element.cloneNode(true);
|
||||
|
||||
for(var attributeName in props) {
|
||||
let animate_element;
|
||||
if(attributeName === 'transform') {
|
||||
animate_element = document.createElementNS("http://www.w3.org/2000/svg", "animateTransform");
|
||||
} else {
|
||||
animate_element = document.createElementNS("http://www.w3.org/2000/svg", "animate");
|
||||
}
|
||||
let current_value = old_values[attributeName] || element.getAttribute(attributeName);
|
||||
let value = props[attributeName];
|
||||
|
||||
let anim_attr = {
|
||||
attributeName: attributeName,
|
||||
from: current_value,
|
||||
to: value,
|
||||
begin: "0s",
|
||||
dur: dur/1000 + "s",
|
||||
values: current_value + ";" + value,
|
||||
keySplines: EASING[easing_type],
|
||||
keyTimes: "0;1",
|
||||
calcMode: "spline",
|
||||
fill: 'freeze'
|
||||
};
|
||||
|
||||
if(type) {
|
||||
anim_attr["type"] = type;
|
||||
}
|
||||
|
||||
for (var i in anim_attr) {
|
||||
animate_element.setAttribute(i, anim_attr[i]);
|
||||
}
|
||||
|
||||
anim_element.appendChild(animate_element);
|
||||
|
||||
if(type) {
|
||||
new_element.setAttribute(attributeName, `translate(${value})`);
|
||||
} else {
|
||||
new_element.setAttribute(attributeName, value);
|
||||
}
|
||||
}
|
||||
|
||||
return [anim_element, new_element];
|
||||
}
|
||||
|
||||
export function transform(element, style) { // eslint-disable-line no-unused-vars
|
||||
element.style.transform = style;
|
||||
element.style.webkitTransform = style;
|
||||
element.style.msTransform = style;
|
||||
element.style.mozTransform = style;
|
||||
element.style.oTransform = style;
|
||||
}
|
||||
|
||||
export function runSVGAnimation(svg_container, elements) {
|
||||
let new_elements = [];
|
||||
let anim_elements = [];
|
||||
|
||||
elements.map(element => {
|
||||
let obj = element[0];
|
||||
let parent = obj.unit.parentNode;
|
||||
|
||||
let anim_element, new_element;
|
||||
|
||||
element[0] = obj.unit;
|
||||
[anim_element, new_element] = animateSVG(...element);
|
||||
|
||||
new_elements.push(new_element);
|
||||
anim_elements.push([anim_element, parent]);
|
||||
|
||||
parent.replaceChild(anim_element, obj.unit);
|
||||
|
||||
if(obj.array) {
|
||||
obj.array[obj.index] = new_element;
|
||||
} else {
|
||||
obj.object[obj.key] = new_element;
|
||||
}
|
||||
});
|
||||
|
||||
let anim_svg = svg_container.cloneNode(true);
|
||||
|
||||
anim_elements.map((anim_element, i) => {
|
||||
anim_element[1].replaceChild(new_elements[i], anim_element[0]);
|
||||
elements[i][0] = new_elements[i];
|
||||
});
|
||||
|
||||
return anim_svg;
|
||||
}
|
||||
18
src/scripts/utils/colors.js
Normal file
18
src/scripts/utils/colors.js
Normal file
@ -0,0 +1,18 @@
|
||||
function limit_color(r){
|
||||
if (r > 255) return 255;
|
||||
else if (r < 0) return 0;
|
||||
return r;
|
||||
}
|
||||
|
||||
export function lighten_darken_color(col, amt) {
|
||||
let usePound = false;
|
||||
if (col[0] == "#") {
|
||||
col = col.slice(1);
|
||||
usePound = true;
|
||||
}
|
||||
let num = parseInt(col,16);
|
||||
let r = limit_color((num >> 16) + amt);
|
||||
let b = limit_color(((num >> 8) & 0x00FF) + amt);
|
||||
let g = limit_color((num & 0x0000FF) + amt);
|
||||
return (usePound?"#":"") + (g | (b << 8) | (r << 16)).toString(16);
|
||||
}
|
||||
34
src/scripts/utils/date-utils.js
Normal file
34
src/scripts/utils/date-utils.js
Normal file
@ -0,0 +1,34 @@
|
||||
// Playing around with dates
|
||||
|
||||
// https://stackoverflow.com/a/11252167/6495043
|
||||
function treat_as_utc(date_str) {
|
||||
let result = new Date(date_str);
|
||||
result.setMinutes(result.getMinutes() - result.getTimezoneOffset());
|
||||
return result;
|
||||
}
|
||||
|
||||
export function get_dd_mm_yyyy(date) {
|
||||
let dd = date.getDate();
|
||||
let mm = date.getMonth() + 1; // getMonth() is zero-based
|
||||
return [
|
||||
(dd>9 ? '' : '0') + dd,
|
||||
(mm>9 ? '' : '0') + mm,
|
||||
date.getFullYear()
|
||||
].join('-');
|
||||
}
|
||||
|
||||
export function get_weeks_between(start_date_str, end_date_str) {
|
||||
return Math.ceil(get_days_between(start_date_str, end_date_str) / 7);
|
||||
}
|
||||
|
||||
export function get_days_between(start_date_str, end_date_str) {
|
||||
let milliseconds_per_day = 24 * 60 * 60 * 1000;
|
||||
return (treat_as_utc(end_date_str) - treat_as_utc(start_date_str)) / milliseconds_per_day;
|
||||
}
|
||||
|
||||
// mutates
|
||||
export function add_days(date, number_of_days) {
|
||||
date.setDate(date.getDate() + number_of_days);
|
||||
}
|
||||
|
||||
// export function get_month_name() {}
|
||||
@ -2,16 +2,6 @@ export default function $(expr, con) {
|
||||
return typeof expr === "string"? (con || document).querySelector(expr) : expr || null;
|
||||
}
|
||||
|
||||
const EASING = {
|
||||
ease: "0.25 0.1 0.25 1",
|
||||
linear: "0 0 1 1",
|
||||
// easein: "0.42 0 1 1",
|
||||
easein: "0.1 0.8 0.2 1",
|
||||
easeout: "0 0 0.58 1",
|
||||
easeinout: "0.42 0 0.58 1"
|
||||
};
|
||||
|
||||
|
||||
$.findNodeIndex = (node) =>
|
||||
{
|
||||
var i = 0;
|
||||
@ -79,100 +69,6 @@ $.createSVG = (tag, o) => {
|
||||
return element;
|
||||
};
|
||||
|
||||
$.runSVGAnimation = (svg_container, elements) => {
|
||||
// let parent = elements[0][0]['unit'].parentNode;
|
||||
|
||||
let new_elements = [];
|
||||
let anim_elements = [];
|
||||
|
||||
elements.map(element => {
|
||||
let obj = element[0];
|
||||
let parent = obj.unit.parentNode;
|
||||
// let index = let findNodeIndex(obj.unit);
|
||||
|
||||
let anim_element, new_element;
|
||||
|
||||
element[0] = obj.unit;
|
||||
[anim_element, new_element] = $.animateSVG(...element);
|
||||
|
||||
new_elements.push(new_element);
|
||||
anim_elements.push([anim_element, parent]);
|
||||
|
||||
parent.replaceChild(anim_element, obj.unit);
|
||||
|
||||
if(obj.array) {
|
||||
obj.array[obj.index] = new_element;
|
||||
} else {
|
||||
obj.object[obj.key] = new_element;
|
||||
}
|
||||
});
|
||||
|
||||
let anim_svg = svg_container.cloneNode(true);
|
||||
|
||||
anim_elements.map((anim_element, i) => {
|
||||
anim_element[1].replaceChild(new_elements[i], anim_element[0]);
|
||||
elements[i][0] = new_elements[i];
|
||||
});
|
||||
|
||||
return anim_svg;
|
||||
};
|
||||
|
||||
$.transform = (element, style)=>{
|
||||
element.style.transform = style;
|
||||
element.style.webkitTransform = style;
|
||||
element.style.msTransform = style;
|
||||
element.style.mozTransform = style;
|
||||
element.style.oTransform = style;
|
||||
};
|
||||
|
||||
$.animateSVG = (element, props, dur, easing_type="linear", type=undefined, old_values={}) => {
|
||||
|
||||
let anim_element = element.cloneNode(true);
|
||||
let new_element = element.cloneNode(true);
|
||||
|
||||
for(var attributeName in props) {
|
||||
let animate_element;
|
||||
if(attributeName === 'transform') {
|
||||
animate_element = document.createElementNS("http://www.w3.org/2000/svg", "animateTransform");
|
||||
} else {
|
||||
animate_element = document.createElementNS("http://www.w3.org/2000/svg", "animate");
|
||||
}
|
||||
let current_value = old_values[attributeName] || element.getAttribute(attributeName);
|
||||
let value = props[attributeName];
|
||||
|
||||
let anim_attr = {
|
||||
attributeName: attributeName,
|
||||
from: current_value,
|
||||
to: value,
|
||||
begin: "0s",
|
||||
dur: dur/1000 + "s",
|
||||
values: current_value + ";" + value,
|
||||
keySplines: EASING[easing_type],
|
||||
keyTimes: "0;1",
|
||||
calcMode: "spline",
|
||||
fill: 'freeze'
|
||||
};
|
||||
|
||||
if(type) {
|
||||
anim_attr["type"] = type;
|
||||
}
|
||||
|
||||
for (var i in anim_attr) {
|
||||
animate_element.setAttribute(i, anim_attr[i]);
|
||||
}
|
||||
|
||||
anim_element.appendChild(animate_element);
|
||||
|
||||
if(type) {
|
||||
new_element.setAttribute(attributeName, `translate(${value})`);
|
||||
} else {
|
||||
new_element.setAttribute(attributeName, value);
|
||||
}
|
||||
}
|
||||
|
||||
return [anim_element, new_element];
|
||||
};
|
||||
|
||||
$.offset = (element) => {
|
||||
let rect = element.getBoundingClientRect();
|
||||
return {
|
||||
148
src/scripts/utils/draw.js
Normal file
148
src/scripts/utils/draw.js
Normal file
@ -0,0 +1,148 @@
|
||||
import $ from './dom';
|
||||
|
||||
export var UnitRenderer = (function() {
|
||||
var UnitRenderer = function(total_height, zero_line, avg_unit_width) {
|
||||
this.total_height = total_height;
|
||||
this.zero_line = zero_line;
|
||||
this.avg_unit_width = avg_unit_width;
|
||||
};
|
||||
|
||||
function get_bar_height_and_y_attr(y_top, zero_line, total_height) {
|
||||
let height, y;
|
||||
if (y_top <= zero_line) {
|
||||
height = zero_line - y_top;
|
||||
y = y_top;
|
||||
|
||||
// In case of invisible bars
|
||||
if(height === 0) {
|
||||
height = total_height * 0.01;
|
||||
y -= height;
|
||||
}
|
||||
} else {
|
||||
height = y_top - zero_line;
|
||||
y = zero_line;
|
||||
|
||||
// In case of invisible bars
|
||||
if(height === 0) {
|
||||
height = total_height * 0.01;
|
||||
}
|
||||
}
|
||||
|
||||
return [height, y];
|
||||
}
|
||||
|
||||
UnitRenderer.prototype = {
|
||||
draw_bar: function (x, y_top, args, color, index, dataset_index, no_of_datasets) {
|
||||
let total_width = this.avg_unit_width - args.space_width;
|
||||
let start_x = x - total_width/2;
|
||||
|
||||
let width = total_width / no_of_datasets;
|
||||
let current_x = start_x + width * dataset_index;
|
||||
|
||||
let [height, y] = get_bar_height_and_y_attr(y_top, this.zero_line, this.total_height);
|
||||
|
||||
return $.createSVG('rect', {
|
||||
className: `bar mini fill ${color}`,
|
||||
'data-point-index': index,
|
||||
x: current_x,
|
||||
y: y,
|
||||
width: width,
|
||||
height: height
|
||||
});
|
||||
},
|
||||
|
||||
draw_dot: function(x, y, args, color, index) {
|
||||
return $.createSVG('circle', {
|
||||
className: `fill ${color}`,
|
||||
'data-point-index': index,
|
||||
cx: x,
|
||||
cy: y,
|
||||
r: args.radius
|
||||
});
|
||||
},
|
||||
|
||||
animate_bar: function(bar_obj, x, y_top, index, no_of_datasets) {
|
||||
let start = x - this.avg_unit_width/4;
|
||||
let width = (this.avg_unit_width/2)/no_of_datasets;
|
||||
let [height, y] = get_bar_height_and_y_attr(y_top, this.zero_line, this.total_height);
|
||||
|
||||
x = start + (width * index);
|
||||
|
||||
return [bar_obj, {width: width, height: height, x: x, y: y}, 350, "easein"];
|
||||
// bar.animate({height: args.new_height, y: y_top}, 350, mina.easein);
|
||||
},
|
||||
|
||||
animate_dot: function(dot_obj, x, y_top) {
|
||||
return [dot_obj, {cx: x, cy: y_top}, 350, "easein"];
|
||||
// dot.animate({cy: y_top}, 350, mina.easein);
|
||||
}
|
||||
};
|
||||
|
||||
return UnitRenderer;
|
||||
})();
|
||||
|
||||
|
||||
export function make_x_line(height, text_start_at, point, label_class, axis_line_class, x_pos) {
|
||||
let line = $.createSVG('line', {
|
||||
x1: 0,
|
||||
x2: 0,
|
||||
y1: 0,
|
||||
y2: height
|
||||
});
|
||||
|
||||
let text = $.createSVG('text', {
|
||||
className: label_class,
|
||||
x: 0,
|
||||
y: text_start_at,
|
||||
dy: '.71em',
|
||||
innerHTML: point
|
||||
});
|
||||
|
||||
let x_line = $.createSVG('g', {
|
||||
className: `tick ${axis_line_class}`,
|
||||
transform: `translate(${ x_pos }, 0)`
|
||||
});
|
||||
|
||||
x_line.appendChild(line);
|
||||
x_line.appendChild(text);
|
||||
|
||||
return x_line;
|
||||
}
|
||||
|
||||
export function make_y_line(start_at, width, text_end_at, point, label_class, axis_line_class, y_pos, darker=false, line_type="") {
|
||||
let line = $.createSVG('line', {
|
||||
className: line_type === "dashed" ? "dashed": "",
|
||||
x1: start_at,
|
||||
x2: width,
|
||||
y1: 0,
|
||||
y2: 0
|
||||
});
|
||||
|
||||
let text = $.createSVG('text', {
|
||||
className: label_class,
|
||||
x: text_end_at,
|
||||
y: 0,
|
||||
dy: '.32em',
|
||||
innerHTML: point+""
|
||||
});
|
||||
|
||||
let y_line = $.createSVG('g', {
|
||||
className: `tick ${axis_line_class}`,
|
||||
transform: `translate(0, ${y_pos})`,
|
||||
'stroke-opacity': 1
|
||||
});
|
||||
|
||||
if(darker) {
|
||||
line.style.stroke = "rgba(27, 31, 35, 0.6)";
|
||||
}
|
||||
|
||||
y_line.appendChild(line);
|
||||
y_line.appendChild(text);
|
||||
|
||||
return y_line;
|
||||
}
|
||||
|
||||
export function get_anim_x_line() {}
|
||||
|
||||
export function get_anim_y_line() {}
|
||||
|
||||
@ -1,7 +1,16 @@
|
||||
/**
|
||||
* Returns the value of a number upto 2 decimal places.
|
||||
* @param {Number} d Any number
|
||||
*/
|
||||
export function float_2(d) {
|
||||
return parseFloat(d.toFixed(2));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not two given arrays are equal.
|
||||
* @param {Array} arr1 First array
|
||||
* @param {Array} arr2 Second array
|
||||
*/
|
||||
export function arrays_equal(arr1, arr2) {
|
||||
if(arr1.length !== arr2.length) return false;
|
||||
let are_equal = true;
|
||||
@ -11,28 +20,9 @@ export function arrays_equal(arr1, arr2) {
|
||||
return are_equal;
|
||||
}
|
||||
|
||||
function limitColor(r){
|
||||
if (r > 255) return 255;
|
||||
else if (r < 0) return 0;
|
||||
return r;
|
||||
}
|
||||
|
||||
export function lightenDarkenColor(col,amt) {
|
||||
let usePound = false;
|
||||
if (col[0] == "#") {
|
||||
col = col.slice(1);
|
||||
usePound = true;
|
||||
}
|
||||
let num = parseInt(col,16);
|
||||
let r = limitColor((num >> 16) + amt);
|
||||
let b = limitColor(((num >> 8) & 0x00FF) + amt);
|
||||
let g = limitColor((num & 0x0000FF) + amt);
|
||||
return (usePound?"#":"") + (g | (b << 8) | (r << 16)).toString(16);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shuffles array in place. ES6 version
|
||||
* @param {Array} a items An array containing the items.
|
||||
* @param {Array} array An array containing the items.
|
||||
*/
|
||||
export function shuffle(array) {
|
||||
// Awesomeness: https://bost.ocks.org/mike/shuffle/
|
||||
@ -43,4 +33,15 @@ export function shuffle(array) {
|
||||
let j = Math.floor(Math.random() * (i + 1));
|
||||
[array[i], array[j]] = [array[j], array[i]];
|
||||
}
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns pixel width of string.
|
||||
* @param {String} string
|
||||
* @param {Number} char_width Width of single char in pixels
|
||||
*/
|
||||
export function get_string_width(string, char_width) {
|
||||
return (string+"").length * char_width;
|
||||
}
|
||||
144
src/scripts/utils/intervals.js
Normal file
144
src/scripts/utils/intervals.js
Normal file
@ -0,0 +1,144 @@
|
||||
export function divide_if_more_than_five(number) {
|
||||
return (number < 5) ? number : number / 2;
|
||||
}
|
||||
|
||||
export function calc_whole_parts(value, divisor) {
|
||||
return Math.ceil(value / divisor);
|
||||
}
|
||||
|
||||
export function make_even(number) {
|
||||
return (number % 2 !== 0) ? ++number : number;
|
||||
}
|
||||
|
||||
export function calc_part_size(value) {
|
||||
// take care of fractions
|
||||
return Math.pow(10, ((value+"").length - 1));
|
||||
}
|
||||
|
||||
export function calc_upper_bound(value) {
|
||||
//
|
||||
}
|
||||
|
||||
export function clump_intervals(start, interval_size, count) {
|
||||
let intervals = [];
|
||||
for(var i = 0; i <= count; i++){
|
||||
intervals.push(start);
|
||||
start += interval_size;
|
||||
}
|
||||
return intervals;
|
||||
}
|
||||
|
||||
// export function calc_intervals() {
|
||||
// //
|
||||
// }
|
||||
|
||||
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;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user