Merge pull request #62 from frappe/develop

Develop
This commit is contained in:
Prateeksha Singh 2017-11-09 03:10:54 +05:30 committed by GitHub
commit dadc1f8384
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 1662 additions and 1611 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -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);
}
};
}
}

View File

@ -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() { }
}

View File

@ -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() {}
}

View File

@ -1,5 +1,5 @@
import AxisChart from './AxisChart';
import $ from '../helpers/dom';
import $ from '../utils/dom';
export default class LineChart extends AxisChart {
constructor(args) {

View File

@ -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 = [];

View File

@ -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]);
}

View File

@ -1,4 +1,4 @@
import $ from '../helpers/dom';
import $ from '../utils/dom';
export default class SvgTip {
constructor({

View 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;
}

View 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);
}

View 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() {}

View File

@ -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
View 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() {}

View File

@ -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;
}

View 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;
}