Browse Source

Merge pull request #75 from frappe/develop

Develop
tags/1.2.0
Prateeksha Singh 7 years ago
committed by GitHub
parent
commit
006186b1da
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 1162 additions and 1010 deletions
  1. +534
    -459
      dist/frappe-charts.esm.js
  2. +1
    -1
      dist/frappe-charts.min.cjs.js
  3. +1
    -1
      dist/frappe-charts.min.esm.js
  4. +1
    -1
      dist/frappe-charts.min.iife.js
  5. +1
    -1
      docs/assets/js/frappe-charts.min.js
  6. +40
    -55
      src/scripts/charts/AxisChart.js
  7. +1
    -1
      src/scripts/charts/BarChart.js
  8. +63
    -57
      src/scripts/charts/BaseChart.js
  9. +31
    -46
      src/scripts/charts/Heatmap.js
  10. +12
    -54
      src/scripts/charts/LineChart.js
  11. +9
    -11
      src/scripts/charts/PieChart.js
  12. +58
    -101
      src/scripts/utils/animate.js
  13. +101
    -0
      src/scripts/utils/animation.js
  14. +29
    -26
      src/scripts/utils/colors.js
  15. +11
    -11
      src/scripts/utils/date-utils.js
  16. +1
    -27
      src/scripts/utils/dom.js
  17. +23
    -0
      src/scripts/utils/draw-utils.js
  18. +179
    -90
      src/scripts/utils/draw.js
  19. +8
    -8
      src/scripts/utils/helpers.js
  20. +58
    -58
      src/scripts/utils/intervals.js
  21. +0
    -2
      src/styles/charts.less

+ 534
- 459
dist/frappe-charts.esm.js
File diff suppressed because it is too large
View File


+ 1
- 1
dist/frappe-charts.min.cjs.js
File diff suppressed because it is too large
View File


+ 1
- 1
dist/frappe-charts.min.esm.js
File diff suppressed because it is too large
View File


+ 1
- 1
dist/frappe-charts.min.iife.js
File diff suppressed because it is too large
View File


+ 1
- 1
docs/assets/js/frappe-charts.min.js
File diff suppressed because it is too large
View File


+ 40
- 55
src/scripts/charts/AxisChart.js View File

@@ -1,8 +1,9 @@
import $ from '../utils/dom';
import { UnitRenderer, make_x_line, make_y_line } from '../utils/draw';
import { runSVGAnimation } from '../utils/animate';
import { calc_intervals } from '../utils/intervals';
import { float_2, arrays_equal, get_string_width } from '../utils/helpers';
import { UnitRenderer, makeXLine, makeYLine } from '../utils/draw';
import { Animator } from '../utils/animate';
import { runSVGAnimation } from '../utils/animation';
import { calcIntervals } from '../utils/intervals';
import { floatTwo, arraysEqual, getStringWidth } from '../utils/helpers';
import BaseChart from './BaseChart';

export default class AxisChart extends BaseChart {
@@ -41,7 +42,7 @@ export default class AxisChart extends BaseChart {
this.x_old_axis_positions = this.x_axis_positions.slice();
}
this.x_axis_positions = this.x.map((d, i) =>
float_2(this.x_offset + i * this.avg_unit_width));
floatTwo(this.x_offset + i * this.avg_unit_width));

if(!this.x_old_axis_positions) {
this.x_old_axis_positions = this.x_axis_positions.slice();
@@ -59,7 +60,7 @@ export default class AxisChart extends BaseChart {
values = values.concat(this.y_sums);
}

this.y_axis_values = calc_intervals(values, this.type === 'line');
this.y_axis_values = calcIntervals(values, this.type === 'line');

if(!this.y_old_axis_values) {
this.y_old_axis_values = this.y_axis_values.slice();
@@ -106,23 +107,21 @@ export default class AxisChart extends BaseChart {
}

setup_marker_components() {
this.y_axis_group = $.createSVG('g', {className: 'y axis', inside: this.draw_area});
this.x_axis_group = $.createSVG('g', {className: 'x axis', inside: this.draw_area});
this.specific_y_group = $.createSVG('g', {className: 'specific axis', inside: this.draw_area});
this.y_axis_group = this.makeDrawAreaComponent('y axis');
this.x_axis_group = this.makeDrawAreaComponent('x axis');
this.specific_y_group = this.makeDrawAreaComponent('specific axis');
}

setup_aggregation_components() {
this.sum_group = $.createSVG('g', {className: 'data-points', inside: this.draw_area});
this.average_group = $.createSVG('g', {className: 'chart-area', inside: this.draw_area});
this.sum_group = this.makeDrawAreaComponent('data-points');
this.average_group = this.makeDrawAreaComponent('chart-area');
}

setup_graph_components() {
this.svg_units_groups = [];
this.y.map((d, i) => {
this.svg_units_groups[i] = $.createSVG('g', {
className: 'data-points data-points-' + i,
inside: this.draw_area
});
this.svg_units_groups[i] = this.makeDrawAreaComponent(
'data-points data-points-' + i);
});
}

@@ -160,7 +159,7 @@ export default class AxisChart extends BaseChart {

this.x_axis_group.textContent = '';
this.x.map((point, i) => {
let space_taken = get_string_width(point, char_width) + 2;
let space_taken = getStringWidth(point, char_width) + 2;
if(space_taken > allowed_space) {
if(this.is_series) {
// Skip some axis lines if X axis is a series
@@ -176,7 +175,7 @@ export default class AxisChart extends BaseChart {
}
}
this.x_axis_group.appendChild(
make_x_line(
makeXLine(
height,
text_start_at,
point,
@@ -201,7 +200,7 @@ export default class AxisChart extends BaseChart {
this.y_axis_group.textContent = '';
this.y_axis_values.map((value, i) => {
this.y_axis_group.appendChild(
make_y_line(
makeYLine(
start_at,
width,
text_end_at,
@@ -303,7 +302,7 @@ export default class AxisChart extends BaseChart {
let unit_renderer = new UnitRenderer(this.height, this.zero_line, this.avg_unit_width);

y_values.map((y, i) => {
let data_unit = unit_renderer['draw_' + unit.type](
let data_unit = unit_renderer[unit.type](
x_values[i],
y,
unit.args,
@@ -325,7 +324,7 @@ export default class AxisChart extends BaseChart {
this.specific_y_group.textContent = '';
this.specific_values.map(d => {
this.specific_y_group.appendChild(
make_y_line(
makeYLine(
0,
this.width,
this.width + 5,
@@ -407,8 +406,8 @@ export default class AxisChart extends BaseChart {

this.make_new_units_for_dataset(
this.x_axis_positions,
this.y_sums.map( val => float_2(this.zero_line - val * this.multiplier)),
'light-grey',
this.y_sums.map( val => floatTwo(this.zero_line - val * this.multiplier)),
'#f0f4f7',
0,
1,
this.sum_group,
@@ -482,17 +481,23 @@ export default class AxisChart extends BaseChart {
this.setup_x();
this.setup_y();

// Change in data, so calculate dependencies
this.calc_y_dependencies();

// Got the values? Now begin drawing
this.animator = new Animator(this.height, this.width, this.zero_line, this.avg_unit_width);

// Animate only if positions have changed
if(!arrays_equal(this.x_old_axis_positions, this.x_axis_positions)) {
if(!arraysEqual(this.x_old_axis_positions, this.x_axis_positions)) {
this.make_x_axis(true);
setTimeout(() => {
if(!this.updating) this.make_x_axis();
}, 350);
}

if(!arrays_equal(this.y_old_axis_values, this.y_axis_values) ||
if(!arraysEqual(this.y_old_axis_values, this.y_axis_values) ||
(this.old_specific_values &&
!arrays_equal(this.old_specific_values, this.specific_values))) {
!arraysEqual(this.old_specific_values, this.specific_values))) {

this.make_y_axis(true);
setTimeout(() => {
@@ -503,9 +508,6 @@ export default class AxisChart extends BaseChart {
}, 350);
}

// Change in data, so calculate dependencies
this.calc_y_dependencies();

this.animate_graphs();

// Trigger animation with the animatable elements in this.elements_to_animate
@@ -574,35 +576,18 @@ export default class AxisChart extends BaseChart {
}

animate_path(d, i, old_x, old_y, new_x, new_y) {
// Animate path
const new_points_list = new_y.map((y, i) => (new_x[i] + ',' + y));
const new_path_str = new_points_list.join("L");

const path_args = [{unit: d.path, object: d, key: 'path'}, {d:"M"+new_path_str}, 350, "easein"];
this.elements_to_animate.push(path_args);

// Animate region
if(d.region_path) {
let reg_start_pt = `0,${this.zero_line}L`;
let reg_end_pt = `L${this.width},${this.zero_line}`;

const region_args = [
{unit: d.region_path, object: d, key: 'region_path'},
{d:"M" + reg_start_pt + new_path_str + reg_end_pt},
350,
"easein"
];
this.elements_to_animate.push(region_args);
}
const newPointsList = new_y.map((y, i) => (new_x[i] + ',' + y));
const newPathStr = newPointsList.join("L");
this.elements_to_animate = this.elements_to_animate
.concat(this.animator['path'](d, newPathStr));
}

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(unit_renderer['animate_' + type](
this.elements_to_animate.push(this.animator[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],
@@ -626,8 +611,8 @@ export default class AxisChart extends BaseChart {
const last_new_y_pos = new_y[new_y.length - 1];

if(this.no_of_extra_pts >= 0) {
// First substitute current path with a squiggled one (looking the same but
// having more points at end),
// First substitute current path with a squiggled one
// (that looks the same but has more points at end),
// then animate to stretch it later to new points
// (new points already have more points)

@@ -666,7 +651,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 = make_x_line(
const x_line = makeXLine(
height,
text_start_at,
value, // new value
@@ -792,7 +777,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 = make_y_line(
const y_line = makeYLine(
start_at,
width,
text_end_at,
@@ -837,7 +822,7 @@ export default class AxisChart extends BaseChart {
calc_y_dependencies() {
this.y_min_tops = new Array(this.x_axis_positions.length).fill(9999);
this.y.map(d => {
d.y_tops = d.values.map( val => float_2(this.zero_line - val * this.multiplier));
d.y_tops = d.values.map( val => floatTwo(this.zero_line - val * this.multiplier));
d.y_tops.map( (y_top, i) => {
if(y_top < this.y_min_tops[i]) {
this.y_min_tops[i] = y_top;


+ 1
- 1
src/scripts/charts/BarChart.js View File

@@ -16,7 +16,7 @@ export default class BarChart extends AxisChart {
this.unit_args = {
type: 'bar',
args: {
space_width: this.avg_unit_width/2,
spaceWidth: this.avg_unit_width/2,
}
};
}


+ 63
- 57
src/scripts/charts/BaseChart.js View File

@@ -1,9 +1,31 @@
import SvgTip from '../objects/SvgTip';
import $ from '../utils/dom';
import { get_string_width } from '../utils/helpers';
import { get_color } from '../utils/colors';
import { makeSVGContainer, makeSVGDefs, makeSVGGroup } from '../utils/draw';
import { getStringWidth } from '../utils/helpers';
import { getColor, DEFAULT_COLORS } from '../utils/colors';
import Chart from '../charts';

const ALL_CHART_TYPES = ['line', 'scatter', 'bar', 'percentage', 'heatmap', 'pie'];

const COMPATIBLE_CHARTS = {
bar: ['line', 'scatter', 'percentage', 'pie'],
line: ['scatter', 'bar', 'percentage', 'pie'],
pie: ['line', 'scatter', 'percentage', 'bar'],
scatter: ['line', 'bar', 'percentage', 'pie'],
percentage: ['bar', 'line', 'scatter', 'pie'],
heatmap: []
};

// TODO: Needs structure as per only labels/datasets
const COLOR_COMPATIBLE_CHARTS = {
bar: ['line', 'scatter'],
line: ['scatter', 'bar'],
pie: ['percentage'],
scatter: ['line', 'bar'],
percentage: ['pie'],
heatmap: []
};

export default class BaseChart {
constructor({
height = 240,
@@ -16,7 +38,7 @@ export default class BaseChart {
is_navigable = 0,
has_legend = 0,

type = '', // eslint-disable-line no-unused-vars
type = '',

parent,
data
@@ -37,55 +59,24 @@ export default class BaseChart {
this.current_index = 0;
}
this.has_legend = has_legend;
this.colors = colors;

const list = type === 'percentage' || type === 'pie'
? this.data.labels
: this.data.datasets;

if(!this.colors || (list && this.colors.length < list.length)) {
this.colors = ['light-blue', 'blue', 'violet', 'red', 'orange',
'yellow', 'green', 'light-green', 'purple', 'magenta'];
}
this.colors = this.colors.map(color => get_color(color));

this.chart_types = ['line', 'scatter', 'bar', 'percentage', 'heatmap', 'pie'];

this.setColors(colors, type);
this.set_margins(height);
}

get_different_chart(type) {
if(!this.chart_types.includes(type)) {
console.error(`'${type}' is not a valid chart type.`);
}
if(type === this.type) return;

// Only across compatible types
let compatible_types = {
bar: ['line', 'scatter', 'percentage', 'pie'],
line: ['scatter', 'bar', 'percentage', 'pie'],
pie: ['line', 'scatter', 'percentage', 'bar'],
scatter: ['line', 'bar', 'percentage', 'pie'],
percentage: ['bar', 'line', 'scatter', 'pie'],
heatmap: []
};

// Only across compatible colors types
let color_compatible_types = {
bar: ['line', 'scatter'],
line: ['scatter', 'bar'],
pie: ['percentage'],
scatter: ['line', 'bar'],
percentage: ['pie'],
heatmap: []
};
if(!ALL_CHART_TYPES.includes(type)) {
console.error(`'${type}' is not a valid chart type.`);
}

if(!compatible_types[this.type].includes(type)) {
if(!COMPATIBLE_CHARTS[this.type].includes(type)) {
console.error(`'${this.type}' chart cannot be converted to a '${type}' chart.`);
}

// whether the new chart can use the existing colors
const use_color = color_compatible_types[this.type].includes(type);
const use_color = COLOR_COMPATIBLE_CHARTS[this.type].includes(type);

// Okay, this is anticlimactic
// this function will need to actually be 'change_chart_type(type)'
@@ -100,6 +91,21 @@ export default class BaseChart {
});
}

setColors(colors, type) {
this.colors = colors;

// TODO: Needs structure as per only labels/datasets
const list = type === 'percentage' || type === 'pie'
? this.data.labels
: this.data.datasets;

if(!this.colors || (list && this.colors.length < list.length)) {
this.colors = DEFAULT_COLORS;
}

this.colors = this.colors.map(color => getColor(color));
}

set_margins(height) {
this.base_height = height;
this.height = height - 40;
@@ -155,7 +161,7 @@ export default class BaseChart {
let special_values_width = 0;
let char_width = 8;
this.specific_values.map(val => {
let str_width = get_string_width((val.title + ""), char_width);
let str_width = getStringWidth((val.title + ""), char_width);
if(str_width > special_values_width) {
special_values_width = str_width - 40;
}
@@ -187,26 +193,22 @@ export default class BaseChart {
}

make_chart_area() {
this.svg = $.createSVG('svg', {
className: 'chart',
inside: this.chart_wrapper,
width: this.base_width,
height: this.base_height
});

this.svg_defs = $.createSVG('defs', {
inside: this.svg,
});

this.svg = makeSVGContainer(
this.chart_wrapper,
'chart',
this.base_width,
this.base_height
);
this.svg_defs = makeSVGDefs(this.svg);
return this.svg;
}

make_draw_area() {
this.draw_area = $.createSVG("g", {
className: this.type + '-chart',
inside: this.svg,
transform: `translate(${this.translate_x}, ${this.translate_y})`
});
this.draw_area = makeSVGGroup(
this.svg,
this.type + '-chart',
`translate(${this.translate_x}, ${this.translate_y})`
);
}

setup_components() { }
@@ -295,4 +297,8 @@ export default class BaseChart {

// Objects
setup_utils() { }

makeDrawAreaComponent(className, transform='') {
return makeSVGGroup(this.draw_area, className, transform);
}
}

+ 31
- 46
src/scripts/charts/Heatmap.js View File

@@ -1,8 +1,8 @@
import BaseChart from './BaseChart';
import $ from '../utils/dom';
import { add_days, get_dd_mm_yyyy, get_weeks_between } from '../utils/date-utils';
import { calc_distribution, get_max_checkpoint } from '../utils/intervals';
import { is_valid_color } from '../utils/colors';
import { makeSVGGroup, makeHeatSquare, makeText } from '../utils/draw';
import { addDays, getDdMmYyyy, getWeeksBetween } from '../utils/date-utils';
import { calcDistribution, getMaxCheckpoint } from '../utils/intervals';
import { isValidColor } from '../utils/colors';

export default class Heatmap extends BaseChart {
constructor({
@@ -25,14 +25,14 @@ export default class Heatmap extends BaseChart {
this.count_label = count_label;

let today = new Date();
this.start = start || add_days(today, 365);
this.start = start || addDays(today, 365);

legend_colors = legend_colors.slice(0, 5);
this.legend_colors = this.validate_colors(legend_colors)
? legend_colors
: ['#ebedf0', '#c6e48b', '#7bc96f', '#239a3b', '#196127'];

// Hardcoded for a fixed 5-color theme,
// Fixed 5-color theme,
// More colors are difficult to parse visually
this.distribution_size = 5;

@@ -45,7 +45,7 @@ export default class Heatmap extends BaseChart {

let valid = 1;
colors.forEach(function(string) {
if(!is_valid_color(string)) {
if(!isValidColor(string)) {
valid = 0;
console.warn('"' + string + '" is not a valid color.');
}
@@ -64,12 +64,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) {
add_days(this.first_week_start, (-1) * this.first_week_start.getDay());
addDays(this.first_week_start, (-1) * this.first_week_start.getDay());
}
if(this.last_week_start.getDay() !== 7) {
add_days(this.last_week_start, (-1) * this.last_week_start.getDay());
addDays(this.last_week_start, (-1) * this.last_week_start.getDay());
}
this.no_of_cols = get_weeks_between(this.first_week_start + '', this.last_week_start + '') + 1;
this.no_of_cols = getWeeksBetween(this.first_week_start + '', this.last_week_start + '') + 1;
}

set_width() {
@@ -81,15 +81,13 @@ export default class Heatmap extends BaseChart {
}

setup_components() {
this.domain_label_group = $.createSVG("g", {
className: "domain-label-group chart-label",
inside: this.draw_area
});
this.data_groups = $.createSVG("g", {
className: "data-groups",
inside: this.draw_area,
transform: `translate(0, 20)`
});
this.domain_label_group = this.makeDrawAreaComponent(
'domain-label-group chart-label');

this.data_groups = this.makeDrawAreaComponent(
'data-groups',
`translate(0, 20)`
);
}

setup_values() {
@@ -97,7 +95,7 @@ export default class Heatmap extends BaseChart {
this.data_groups.textContent = '';

let data_values = Object.keys(this.data).map(key => this.data[key]);
this.distribution = calc_distribution(data_values, this.distribution_size);
this.distribution = calcDistribution(data_values, this.distribution_size);

this.month_names = ["January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"
@@ -129,7 +127,7 @@ export default class Heatmap extends BaseChart {
this.months.push(this.current_month + '');
this.month_weeks[this.current_month] = 1;
}
add_days(current_week_sunday, 7);
addDays(current_week_sunday, 7);
}
this.render_month_labels();
}
@@ -144,10 +142,7 @@ export default class Heatmap extends BaseChart {
let month_change = 0;
let week_col_change = 0;

let data_group = $.createSVG("g", {
className: "data-group",
inside: this.data_groups
});
let data_group = makeSVGGroup(this.data_groups, 'data-group');

for(var y = 0, i = 0; i < no_of_weekdays; i += step, y += (square_side + cell_padding)) {
let data_value = 0;
@@ -165,26 +160,23 @@ export default class Heatmap extends BaseChart {
}

if(data_value) {
color_index = get_max_checkpoint(data_value, this.distribution);
color_index = getMaxCheckpoint(data_value, this.distribution);
}

let x = 13 + (index + week_col_change) * 12;

$.createSVG("rect", {
className: 'day',
inside: data_group,
x: x,
y: y,
width: square_side,
height: square_side,
fill: this.legend_colors[color_index],
'data-date': get_dd_mm_yyyy(current_date),
let dataAttr = {
'data-date': getDdMmYyyy(current_date),
'data-value': data_value,
'data-day': current_date.getDay()
});
};
let heatSquare = makeHeatSquare('day', x, y, square_side,
this.legend_colors[color_index], dataAttr);

data_group.appendChild(heatSquare);

let next_date = new Date(current_date);
add_days(next_date, 1);
addDays(next_date, 1);
if(next_date.getTime() > today_time) break;


@@ -224,15 +216,8 @@ export default class Heatmap extends BaseChart {

this.month_start_points.map((start, i) => {
let month_name = this.month_names[this.months[i]].substring(0, 3);

$.createSVG('text', {
className: 'y-value-text',
inside: this.domain_label_group,
x: start + 12,
y: 10,
dy: '.32em',
innerHTML: month_name
});
let text = makeText('y-value-text', start+12, 10, month_name);
this.domain_label_group.appendChild(text);
});
}



+ 12
- 54
src/scripts/charts/LineChart.js View File

@@ -1,5 +1,5 @@
import AxisChart from './AxisChart';
import $ from '../utils/dom';
import { makeSVGGroup, makePath, makeGradient } from '../utils/draw';

export default class LineChart extends AxisChart {
constructor(args) {
@@ -33,10 +33,10 @@ export default class LineChart extends AxisChart {
setup_path_groups() {
this.paths_groups = [];
this.y.map((d, i) => {
this.paths_groups[i] = $.createSVG('g', {
className: 'path-group path-group-' + i,
inside: this.draw_area
});
this.paths_groups[i] = makeSVGGroup(
this.draw_area,
'path-group path-group-' + i
);
});
}

@@ -68,14 +68,11 @@ export default class LineChart extends AxisChart {

this.paths_groups[i].textContent = '';

d.path = $.createSVG('path', {
inside: this.paths_groups[i],
style: `stroke: ${color}`,
d: "M"+points_str
});
d.path = makePath("M"+points_str, 'line-graph-path', color);
this.paths_groups[i].appendChild(d.path);

if(this.heatline) {
let gradient_id = this.make_gradient(color);
let gradient_id = makeGradient(this.svg_defs, color);
d.path.style.stroke = `url(#${gradient_id})`;
}

@@ -85,49 +82,10 @@ export default class LineChart extends AxisChart {
}

fill_region_for_dataset(d, i, color, points_str) {
let gradient_id = this.make_gradient(color, true);

d.region_path = $.createSVG('path', {
inside: this.paths_groups[i],
className: `region-fill`,
d: "M" + `0,${this.zero_line}L` + points_str + `L${this.width},${this.zero_line}`,
});

d.region_path.style.stroke = "none";
d.region_path.style.fill = `url(#${gradient_id})`;
}

make_gradient(color, lighter = false) {
let gradient_id ='path-fill-gradient' + '-' + color;

let gradient_def = $.createSVG('linearGradient', {
inside: this.svg_defs,
id: gradient_id,
x1: 0,
x2: 0,
y1: 0,
y2: 1
});

let set_gradient_stop = (grad_elem, offset, color, opacity) => {
$.createSVG('stop', {
style: `stop-color: ${color}`,
inside: grad_elem,
'offset': offset,
'stop-opacity': opacity
});
};

let opacities = [1, 0.6, 0.2];

if(lighter) {
opacities = [0.4, 0.2, 0];
}

set_gradient_stop(gradient_def, "0%", color, opacities[0]);
set_gradient_stop(gradient_def, "50%", color, opacities[1]);
set_gradient_stop(gradient_def, "100%", color, opacities[2]);
let gradient_id = makeGradient(this.svg_defs, color, true);
let pathStr = "M" + `0,${this.zero_line}L` + points_str + `L${this.width},${this.zero_line}`;

return gradient_id;
d.regionPath = makePath(pathStr, `region-fill`, 'none', `url(#${gradient_id})`);
this.paths_groups[i].appendChild(d.regionPath);
}
}

+ 9
- 11
src/scripts/charts/PieChart.js View File

@@ -1,7 +1,8 @@
import BaseChart from './BaseChart';
import $ from '../utils/dom';
import { lighten_darken_color } from '../utils/colors';
import { runSVGAnimation, transform } from '../utils/animate';
import { makePath } from '../utils/draw';
import { lightenDarkenColor } from '../utils/colors';
import { runSVGAnimation, transform } from '../utils/animation';
const ANGLE_RATIO = Math.PI / 180;
const FULL_ANGLE = 360;

@@ -93,13 +94,10 @@ export default class PieChart extends BaseChart {
curEnd = endPosition;
}
const curPath = this.makeArcPath(curStart,curEnd);
let slice = $.createSVG('path',{
inside: this.draw_area,
className: 'pie-path',
style: 'transition:transform .3s;',
d: curPath,
fill: this.colors[i]
});
let slice = makePath(curPath, 'pie-path', 'none', this.colors[i]);
slice.style.transition = 'transform .3s;';
this.draw_area.appendChild(slice);

this.slices.push(slice);
this.slicesProperties.push({
startPosition,
@@ -155,7 +153,7 @@ export default class PieChart extends BaseChart {
const color = this.colors[i];
if(flag){
transform(path,this.calTranslateByAngle(this.slicesProperties[i]));
path.setAttribute('fill',lighten_darken_color(color,50));
path.style.fill = lightenDarkenColor(color,50);
let g_off = $.offset(this.svg);
let x = e.pageX - g_off.left + 10;
let y = e.pageY - g_off.top - 10;
@@ -167,7 +165,7 @@ export default class PieChart extends BaseChart {
}else{
transform(path,'translate3d(0,0,0)');
this.tip.hide_tip();
path.setAttribute('fill',color);
path.style.fill = color;
}
}



+ 58
- 101
src/scripts/utils/animate.js View File

@@ -1,101 +1,58 @@
// 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;
}
import { getBarHeightAndYAttr } from './draw-utils';

export function getAnimXLine() {}

export function getAnimYLine() {}

export var Animator = (function() {
var Animator = function(totalHeight, totalWidth, zeroLine, avgUnitWidth) {
// constants
this.totalHeight = totalHeight;
this.totalWidth = totalWidth;

// changeables
this.avgUnitWidth = avgUnitWidth;
this.zeroLine = zeroLine;
};

Animator.prototype = {
bar: function(barObj, x, yTop, index, noOfDatasets) {
let start = x - this.avgUnitWidth/4;
let width = (this.avgUnitWidth/2)/noOfDatasets;
let [height, y] = getBarHeightAndYAttr(yTop, this.zeroLine, this.totalHeight);

x = start + (width * index);

return [barObj, {width: width, height: height, x: x, y: y}, 350, "easein"];
// bar.animate({height: args.newHeight, y: yTop}, 350, mina.easein);
},

dot: function(dotObj, x, yTop) {
return [dotObj, {cx: x, cy: yTop}, 350, "easein"];
// dot.animate({cy: yTop}, 350, mina.easein);
},

path: function(d, pathStr) {
let pathComponents = [];
const animPath = [{unit: d.path, object: d, key: 'path'}, {d:"M"+pathStr}, 350, "easein"];
pathComponents.push(animPath);

if(d.regionPath) {
let regStartPt = `0,${this.zeroLine}L`;
let regEndPt = `L${this.totalWidth}, ${this.zeroLine}`;

const animRegion = [
{unit: d.regionPath, object: d, key: 'regionPath'},
{d:"M" + regStartPt + pathStr + regEndPt},
350,
"easein"
];
pathComponents.push(animRegion);
}

return pathComponents;
},
};

return Animator;
})();

+ 101
- 0
src/scripts/utils/animation.js 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, easingType="linear", type=undefined, oldValues={}) {

let animElement = element.cloneNode(true);
let newElement = element.cloneNode(true);

for(var attributeName in props) {
let animateElement;
if(attributeName === 'transform') {
animateElement = document.createElementNS("http://www.w3.org/2000/svg", "animateTransform");
} else {
animateElement = document.createElementNS("http://www.w3.org/2000/svg", "animate");
}
let currentValue = oldValues[attributeName] || element.getAttribute(attributeName);
let value = props[attributeName];

let animAttr = {
attributeName: attributeName,
from: currentValue,
to: value,
begin: "0s",
dur: dur/1000 + "s",
values: currentValue + ";" + value,
keySplines: EASING[easingType],
keyTimes: "0;1",
calcMode: "spline",
fill: 'freeze'
};

if(type) {
animAttr["type"] = type;
}

for (var i in animAttr) {
animateElement.setAttribute(i, animAttr[i]);
}

animElement.appendChild(animateElement);

if(type) {
newElement.setAttribute(attributeName, `translate(${value})`);
} else {
newElement.setAttribute(attributeName, value);
}
}

return [animElement, newElement];
}

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(svgContainer, elements) {
let newElements = [];
let animElements = [];

elements.map(element => {
let obj = element[0];
let parent = obj.unit.parentNode;

let animElement, newElement;

element[0] = obj.unit;
[animElement, newElement] = animateSVG(...element);

newElements.push(newElement);
animElements.push([animElement, parent]);

parent.replaceChild(animElement, obj.unit);

if(obj.array) {
obj.array[obj.index] = newElement;
} else {
obj.object[obj.key] = newElement;
}
});

let animSvg = svgContainer.cloneNode(true);

animElements.map((animElement, i) => {
animElement[1].replaceChild(newElements[i], animElement[0]);
elements[i][0] = newElements[i];
});

return animSvg;
}

+ 29
- 26
src/scripts/utils/colors.js View File

@@ -1,45 +1,48 @@
function limit_color(r){
const PRESET_COLOR_MAP = {
'light-blue': '#7cd6fd',
'blue': '#5e64ff',
'violet': '#743ee2',
'red': '#ff5858',
'orange': '#ffa00a',
'yellow': '#feef72',
'green': '#28a745',
'light-green': '#98d85b',
'purple': '#b554ff',
'magenta': '#ffa3ef',
'black': '#36114C',
'grey': '#bdd3e6',
'light-grey': '#f0f4f7',
'dark-grey': '#b8c2cc'
};

export const DEFAULT_COLORS = ['light-blue', 'blue', 'violet', 'red', 'orange',
'yellow', 'green', 'light-green', 'purple', 'magenta'];

function limitColor(r){
if (r > 255) return 255;
else if (r < 0) return 0;
return r;
}

export function lighten_darken_color(color, amt) {
let col = get_color(color);
export function lightenDarkenColor(color, amt) {
let col = getColor(color);
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);
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);
}

export function is_valid_color(string) {
export function isValidColor(string) {
// https://stackoverflow.com/a/8027444/6495043
return /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(string);
}

export const color_map = {
'light-blue': '#7cd6fd',
blue: '#5e64ff',
violet: '#743ee2',
red: '#ff5858',
orange: '#ffa00a',
yellow: '#feef72',
green: '#28a745',
'light-green': '#98d85b',
purple: '#b554ff',
magenta: '#ffa3ef',
black: '#36114C',
grey: '#bdd3e6',
'light-grey': '#f0f4f7',
'dark-grey': '#b8c2cc'
};

export const get_color = (color) => {
return color_map[color] || color;
export const getColor = (color) => {
return PRESET_COLOR_MAP[color] || color;
};

+ 11
- 11
src/scripts/utils/date-utils.js View File

@@ -1,13 +1,13 @@
// Playing around with dates

// https://stackoverflow.com/a/11252167/6495043
function treat_as_utc(date_str) {
let result = new Date(date_str);
function treatAsUtc(dateStr) {
let result = new Date(dateStr);
result.setMinutes(result.getMinutes() - result.getTimezoneOffset());
return result;
}

export function get_dd_mm_yyyy(date) {
export function getDdMmYyyy(date) {
let dd = date.getDate();
let mm = date.getMonth() + 1; // getMonth() is zero-based
return [
@@ -17,18 +17,18 @@ export function get_dd_mm_yyyy(date) {
].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 getWeeksBetween(startDateStr, endDateStr) {
return Math.ceil(getDaysBetween(startDateStr, endDateStr) / 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;
export function getDaysBetween(startDateStr, endDateStr) {
let millisecondsPerDay = 24 * 60 * 60 * 1000;
return (treatAsUtc(endDateStr) - treatAsUtc(startDateStr)) / millisecondsPerDay;
}

// mutates
export function add_days(date, number_of_days) {
date.setDate(date.getDate() + number_of_days);
export function addDays(date, numberOfDays) {
date.setDate(date.getDate() + numberOfDays);
}

// export function get_month_name() {}
// export function getMonthName() {}

+ 1
- 27
src/scripts/utils/dom.js View File

@@ -25,6 +25,7 @@ $.create = (tag, o) => {
var ref = $(val);
ref.parentNode.insertBefore(element, ref);
element.appendChild(ref);

} else if (i === "styles") {
if(typeof val === "object") {
Object.keys(val).map(prop => {
@@ -42,33 +43,6 @@ $.create = (tag, o) => {
return element;
};

$.createSVG = (tag, o) => {
var element = document.createElementNS("http://www.w3.org/2000/svg", tag);

for (var i in o) {
var val = o[i];

if (i === "inside") {
$(val).appendChild(element);
}
else if (i === "around") {
var ref = $(val);
ref.parentNode.insertBefore(element, ref);
element.appendChild(ref);
}
else {
if(i === "className") { i = "class"; }
if(i === "innerHTML") {
element['textContent'] = val;
} else {
element.setAttribute(i, val);
}
}
}

return element;
};

$.offset = (element) => {
let rect = element.getBoundingClientRect();
return {


+ 23
- 0
src/scripts/utils/draw-utils.js View File

@@ -0,0 +1,23 @@
export function getBarHeightAndYAttr(yTop, zeroLine, totalHeight) {
let height, y;
if (yTop <= zeroLine) {
height = zeroLine - yTop;
y = yTop;

// In case of invisible bars
if(height === 0) {
height = totalHeight * 0.01;
y -= height;
}
} else {
height = yTop - zeroLine;
y = zeroLine;

// In case of invisible bars
if(height === 0) {
height = totalHeight * 0.01;
}
}

return [height, y];
}

+ 179
- 90
src/scripts/utils/draw.js View File

@@ -1,135 +1,187 @@
import $ from './dom';
import { getBarHeightAndYAttr } from './draw-utils';

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;
};
// Constants used

function $(expr, con) {
return typeof expr === "string"? (con || document).querySelector(expr) : expr || null;
}

function createSVG(tag, o) {
var element = document.createElementNS("http://www.w3.org/2000/svg", tag);

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;
for (var i in o) {
var val = o[i];

// In case of invisible bars
if(height === 0) {
height = total_height * 0.01;
y -= height;
if (i === "inside") {
$(val).appendChild(element);
}
else if (i === "around") {
var ref = $(val);
ref.parentNode.insertBefore(element, ref);
element.appendChild(ref);

} else if (i === "styles") {
if(typeof val === "object") {
Object.keys(val).map(prop => {
element.style[prop] = val[prop];
});
}
} else {
height = y_top - zero_line;
y = zero_line;

// In case of invisible bars
if(height === 0) {
height = total_height * 0.01;
if(i === "className") { i = "class"; }
if(i === "innerHTML") {
element['textContent'] = val;
} else {
element.setAttribute(i, val);
}
}

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;
return element;
}

let width = total_width / no_of_datasets;
let current_x = start_x + width * dataset_index;
function renderVerticalGradient(svgDefElem, gradientId) {
return createSVG('linearGradient', {
inside: svgDefElem,
id: gradientId,
x1: 0,
x2: 0,
y1: 0,
y2: 1
});
}

let [height, y] = get_bar_height_and_y_attr(y_top, this.zero_line, this.total_height);
function setGradientStop(gradElem, offset, color, opacity) {
return createSVG('stop', {
'inside': gradElem,
'style': `stop-color: ${color}`,
'offset': offset,
'stop-opacity': opacity
});
}

return $.createSVG('rect', {
className: `bar mini`,
style: `fill: ${color}`,
'data-point-index': index,
x: current_x,
y: y,
width: width,
height: height
});
},
export function makeSVGContainer(parent, className, width, height) {
return createSVG('svg', {
className: className,
inside: parent,
width: width,
height: height
});
}

draw_dot: function(x, y, args, color, index) {
return $.createSVG('circle', {
style: `fill: ${color}`,
'data-point-index': index,
cx: x,
cy: y,
r: args.radius
});
},
export function makeSVGDefs(svgContainer) {
return createSVG('defs', {
inside: svgContainer,
});
}

export function makeSVGGroup(parent, className, transform='') {
return createSVG('g', {
className: className,
inside: parent,
transform: transform
});
}

export function makePath(pathStr, className='', stroke='none', fill='none') {
return createSVG('path', {
className: className,
d: pathStr,
styles: {
stroke: stroke,
fill: fill
}
});
}

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);
export function makeGradient(svgDefElem, color, lighter = false) {
let gradientId ='path-fill-gradient' + '-' + color;
let gradientDef = renderVerticalGradient(svgDefElem, gradientId);
let opacities = [1, 0.6, 0.2];
if(lighter) {
opacities = [0.4, 0.2, 0];
}

x = start + (width * index);
setGradientStop(gradientDef, "0%", color, opacities[0]);
setGradientStop(gradientDef, "50%", color, opacities[1]);
setGradientStop(gradientDef, "100%", color, opacities[2]);

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);
},
return gradientId;
}

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);
}
export function makeHeatSquare(className, x, y, size, fill='none', data={}) {
let args = {
className: className,
x: x,
y: y,
width: size,
height: size,
fill: fill
};

return UnitRenderer;
})();
Object.keys(data).map(key => {
args[key] = data[key];
});

return createSVG("rect", args);
}

export function makeText(className, x, y, content) {
return createSVG('text', {
className: className,
x: x,
y: y,
dy: '.32em',
innerHTML: content
});
}

export function make_x_line(height, text_start_at, point, label_class, axis_line_class, x_pos) {
let line = $.createSVG('line', {
export function makeXLine(height, textStartAt, point, labelClass, axisLineClass, xPos) {
let line = createSVG('line', {
x1: 0,
x2: 0,
y1: 0,
y2: height
});

let text = $.createSVG('text', {
className: label_class,
let text = createSVG('text', {
className: labelClass,
x: 0,
y: text_start_at,
y: textStartAt,
dy: '.71em',
innerHTML: point
});

let x_line = $.createSVG('g', {
className: `tick ${axis_line_class}`,
transform: `translate(${ x_pos }, 0)`
let xLine = createSVG('g', {
className: `tick ${axisLineClass}`,
transform: `translate(${ xPos }, 0)`
});

x_line.appendChild(line);
x_line.appendChild(text);
xLine.appendChild(line);
xLine.appendChild(text);

return x_line;
return xLine;
}

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,
export function makeYLine(startAt, width, textEndAt, point, labelClass, axisLineClass, yPos, darker=false, lineType="") {
let line = createSVG('line', {
className: lineType === "dashed" ? "dashed": "",
x1: startAt,
x2: width,
y1: 0,
y2: 0
});

let text = $.createSVG('text', {
className: label_class,
x: text_end_at,
let text = createSVG('text', {
className: labelClass,
x: textEndAt,
y: 0,
dy: '.32em',
innerHTML: point+""
});

let y_line = $.createSVG('g', {
className: `tick ${axis_line_class}`,
transform: `translate(0, ${y_pos})`,
let yLine = createSVG('g', {
className: `tick ${axisLineClass}`,
transform: `translate(0, ${yPos})`,
'stroke-opacity': 1
});

@@ -137,13 +189,50 @@ export function make_y_line(start_at, width, text_end_at, point, label_class, ax
line.style.stroke = "rgba(27, 31, 35, 0.6)";
}

y_line.appendChild(line);
y_line.appendChild(text);
yLine.appendChild(line);
yLine.appendChild(text);

return y_line;
return yLine;
}

export function get_anim_x_line() {}
export var UnitRenderer = (function() {
var UnitRenderer = function(totalHeight, zeroLine, avgUnitWidth) {
this.totalHeight = totalHeight;
this.zeroLine = zeroLine;
this.avgUnitWidth = avgUnitWidth;
};

UnitRenderer.prototype = {
bar: function (x, yTop, args, color, index, datasetIndex, noOfDatasets) {
let totalWidth = this.avgUnitWidth - args.spaceWidth;
let startX = x - totalWidth/2;

let width = totalWidth / noOfDatasets;
let currentX = startX + width * datasetIndex;

export function get_anim_y_line() {}
let [height, y] = getBarHeightAndYAttr(yTop, this.zeroLine, this.totalHeight);

return createSVG('rect', {
className: `bar mini`,
style: `fill: ${color}`,
'data-point-index': index,
x: currentX,
y: y,
width: width,
height: height
});
},

dot: function(x, y, args, color, index) {
return createSVG('circle', {
style: `fill: ${color}`,
'data-point-index': index,
cx: x,
cy: y,
r: args.radius
});
}
};

return UnitRenderer;
})();

+ 8
- 8
src/scripts/utils/helpers.js View File

@@ -2,7 +2,7 @@
* Returns the value of a number upto 2 decimal places.
* @param {Number} d Any number
*/
export function float_2(d) {
export function floatTwo(d) {
return parseFloat(d.toFixed(2));
}

@@ -11,13 +11,13 @@ export function float_2(d) {
* @param {Array} arr1 First array
* @param {Array} arr2 Second array
*/
export function arrays_equal(arr1, arr2) {
export function arraysEqual(arr1, arr2) {
if(arr1.length !== arr2.length) return false;
let are_equal = true;
let areEqual = true;
arr1.map((d, i) => {
if(arr2[i] !== d) are_equal = false;
if(arr2[i] !== d) areEqual = false;
});
return are_equal;
return areEqual;
}

/**
@@ -40,8 +40,8 @@ export function shuffle(array) {
/**
* Returns pixel width of string.
* @param {String} string
* @param {Number} char_width Width of single char in pixels
* @param {Number} charWidth Width of single char in pixels
*/
export function get_string_width(string, char_width) {
return (string+"").length * char_width;
export function getStringWidth(string, charWidth) {
return (string+"").length * charWidth;
}

+ 58
- 58
src/scripts/utils/intervals.js View File

@@ -21,77 +21,77 @@ function normalize(x) {
return [sig * man, exp];
}

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;
function getRangeIntervals(max, min=0) {
let upperBound = Math.ceil(max);
let lowerBound = Math.floor(min);
let range = upperBound - lowerBound;

let no_of_parts = range;
let part_size = 1;
let noOfParts = range;
let partSize = 1;

// To avoid too many partitions
if(range > 5) {
if(range % 2 !== 0) {
upper_bound++;
upperBound++;
// Recalc range
range = upper_bound - lower_bound;
range = upperBound - lowerBound;
}
no_of_parts = range/2;
part_size = 2;
noOfParts = range/2;
partSize = 2;
}

// Special case: 1 and 2
if(range <= 2) {
no_of_parts = 4;
part_size = range/no_of_parts;
noOfParts = 4;
partSize = range/noOfParts;
}

// Special case: 0
if(range === 0) {
no_of_parts = 5;
part_size = 1;
noOfParts = 5;
partSize = 1;
}

let intervals = [];
for(var i = 0; i <= no_of_parts; i++){
intervals.push(lower_bound + part_size * i);
for(var i = 0; i <= noOfParts; i++){
intervals.push(lowerBound + partSize * i);
}
return 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;
function getIntervals(maxValue, minValue=0) {
let [normalMaxValue, exponent] = normalize(maxValue);
let normalMinValue = minValue ? minValue/Math.pow(10, exponent): 0;

// Allow only 7 significant digits
normal_max_value = normal_max_value.toFixed(6);
normalMaxValue = normalMaxValue.toFixed(6);

let intervals = get_range_intervals(normal_max_value, normal_min_value);
let intervals = getRangeIntervals(normalMaxValue, normalMinValue);
intervals = intervals.map(value => value * Math.pow(10, exponent));
return intervals;
}

export function calc_intervals(values, with_minimum=false) {
export function calcIntervals(values, withMinimum=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 maxValue = Math.max(...values);
let minValue = Math.min(...values);

// Exponent to be used for pretty print
let exponent = 0, intervals = []; // eslint-disable-line no-unused-vars

function get_positive_first_intervals(max_value, abs_min_value) {
let intervals = get_intervals(max_value);
function getPositiveFirstIntervals(maxValue, absMinValue) {
let intervals = getIntervals(maxValue);

let interval_size = intervals[1] - intervals[0];
let intervalSize = intervals[1] - intervals[0];

// Then unshift the negative values
let value = 0;
for(var i = 1; value < abs_min_value; i++) {
value += interval_size;
for(var i = 1; value < absMinValue; i++) {
value += intervalSize;
intervals.unshift((-1) * value);
}
return intervals;
@@ -99,52 +99,52 @@ export function calc_intervals(values, with_minimum=false) {

// 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);
if(maxValue >= 0 && minValue >= 0) {
exponent = normalize(maxValue)[1];
if(!withMinimum) {
intervals = getIntervals(maxValue);
} else {
intervals = get_intervals(max_value, min_value);
intervals = getIntervals(maxValue, minValue);
}
}

// CASE II: Only min_value negative
// CASE II: Only minValue negative

else if(max_value > 0 && min_value < 0) {
// `with_minimum` irrelevant in this case,
else if(maxValue > 0 && minValue < 0) {
// `withMinimum` irrelevant in this case,
// We'll be handling both sides of zero separately
// (both starting from zero)
// Because ceil() and floor() behave differently
// in those two regions

let abs_min_value = Math.abs(min_value);
let absMinValue = Math.abs(minValue);

if(max_value >= abs_min_value) {
exponent = normalize(max_value)[1];
intervals = get_positive_first_intervals(max_value, abs_min_value);
if(maxValue >= absMinValue) {
exponent = normalize(maxValue)[1];
intervals = getPositiveFirstIntervals(maxValue, absMinValue);
} 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));
// Mirror: maxValue => absMinValue, then change sign
exponent = normalize(absMinValue)[1];
let posIntervals = getPositiveFirstIntervals(absMinValue, maxValue);
intervals = posIntervals.map(d => d * (-1));
}

}

// CASE III: Both non-positive

else if(max_value <= 0 && min_value <= 0) {
else if(maxValue <= 0 && minValue <= 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);
let pseudoMaxValue = Math.abs(minValue);
let pseudoMinValue = Math.abs(maxValue);

exponent = normalize(pseudo_max_value)[1];
if(!with_minimum) {
intervals = get_intervals(pseudo_max_value);
exponent = normalize(pseudoMaxValue)[1];
if(!withMinimum) {
intervals = getIntervals(pseudoMaxValue);
} else {
intervals = get_intervals(pseudo_max_value, pseudo_min_value);
intervals = getIntervals(pseudoMaxValue, pseudoMinValue);
}

intervals = intervals.reverse().map(d => d * (-1));
@@ -153,23 +153,23 @@ export function calc_intervals(values, with_minimum=false) {
return intervals;
}

export function calc_distribution(values, distribution_size) {
export function calcDistribution(values, distributionSize) {
// Assume non-negative values,
// implying distribution minimum at zero

let data_max_value = Math.max(...values);
let dataMaxValue = Math.max(...values);

let distribution_step = 1 / (distribution_size - 1);
let distributionStep = 1 / (distributionSize - 1);
let distribution = [];

for(var i = 0; i < distribution_size; i++) {
let checkpoint = data_max_value * (distribution_step * i);
for(var i = 0; i < distributionSize; i++) {
let checkpoint = dataMaxValue * (distributionStep * i);
distribution.push(checkpoint);
}

return distribution;
}

export function get_max_checkpoint(value, distribution) {
export function getMaxCheckpoint(value, distribution) {
return distribution.filter(d => d < value).length;
}

+ 0
- 2
src/styles/charts.less View File

@@ -45,8 +45,6 @@
color: #98d85b;
}
}


}
.axis, .chart-label {
font-size: 11px;


Loading…
Cancel
Save