Merge pull request #75 from frappe/develop

Develop
This commit is contained in:
Prateeksha Singh 2017-11-19 20:51:43 +05:30 committed by GitHub
commit 006186b1da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 1156 additions and 1004 deletions

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

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,8 +1,9 @@
import $ from '../utils/dom'; import $ from '../utils/dom';
import { UnitRenderer, make_x_line, make_y_line } from '../utils/draw'; import { UnitRenderer, makeXLine, makeYLine } from '../utils/draw';
import { runSVGAnimation } from '../utils/animate'; import { Animator } from '../utils/animate';
import { calc_intervals } from '../utils/intervals'; import { runSVGAnimation } from '../utils/animation';
import { float_2, arrays_equal, get_string_width } from '../utils/helpers'; import { calcIntervals } from '../utils/intervals';
import { floatTwo, arraysEqual, getStringWidth } from '../utils/helpers';
import BaseChart from './BaseChart'; import BaseChart from './BaseChart';
export default class AxisChart extends 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_old_axis_positions = this.x_axis_positions.slice();
} }
this.x_axis_positions = this.x.map((d, i) => 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) { if(!this.x_old_axis_positions) {
this.x_old_axis_positions = this.x_axis_positions.slice(); 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); 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) { if(!this.y_old_axis_values) {
this.y_old_axis_values = this.y_axis_values.slice(); this.y_old_axis_values = this.y_axis_values.slice();
@ -106,23 +107,21 @@ export default class AxisChart extends BaseChart {
} }
setup_marker_components() { setup_marker_components() {
this.y_axis_group = $.createSVG('g', {className: 'y axis', inside: this.draw_area}); this.y_axis_group = this.makeDrawAreaComponent('y axis');
this.x_axis_group = $.createSVG('g', {className: 'x axis', inside: this.draw_area}); this.x_axis_group = this.makeDrawAreaComponent('x axis');
this.specific_y_group = $.createSVG('g', {className: 'specific axis', inside: this.draw_area}); this.specific_y_group = this.makeDrawAreaComponent('specific axis');
} }
setup_aggregation_components() { setup_aggregation_components() {
this.sum_group = $.createSVG('g', {className: 'data-points', inside: this.draw_area}); this.sum_group = this.makeDrawAreaComponent('data-points');
this.average_group = $.createSVG('g', {className: 'chart-area', inside: this.draw_area}); this.average_group = this.makeDrawAreaComponent('chart-area');
} }
setup_graph_components() { setup_graph_components() {
this.svg_units_groups = []; this.svg_units_groups = [];
this.y.map((d, i) => { this.y.map((d, i) => {
this.svg_units_groups[i] = $.createSVG('g', { this.svg_units_groups[i] = this.makeDrawAreaComponent(
className: 'data-points data-points-' + i, 'data-points data-points-' + i);
inside: this.draw_area
});
}); });
} }
@ -160,7 +159,7 @@ export default class AxisChart extends BaseChart {
this.x_axis_group.textContent = ''; this.x_axis_group.textContent = '';
this.x.map((point, i) => { 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(space_taken > allowed_space) {
if(this.is_series) { if(this.is_series) {
// Skip some axis lines if X axis is a 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( this.x_axis_group.appendChild(
make_x_line( makeXLine(
height, height,
text_start_at, text_start_at,
point, point,
@ -201,7 +200,7 @@ export default class AxisChart extends BaseChart {
this.y_axis_group.textContent = ''; this.y_axis_group.textContent = '';
this.y_axis_values.map((value, i) => { this.y_axis_values.map((value, i) => {
this.y_axis_group.appendChild( this.y_axis_group.appendChild(
make_y_line( makeYLine(
start_at, start_at,
width, width,
text_end_at, 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); let unit_renderer = new UnitRenderer(this.height, this.zero_line, this.avg_unit_width);
y_values.map((y, i) => { y_values.map((y, i) => {
let data_unit = unit_renderer['draw_' + unit.type]( let data_unit = unit_renderer[unit.type](
x_values[i], x_values[i],
y, y,
unit.args, unit.args,
@ -325,7 +324,7 @@ export default class AxisChart extends BaseChart {
this.specific_y_group.textContent = ''; this.specific_y_group.textContent = '';
this.specific_values.map(d => { this.specific_values.map(d => {
this.specific_y_group.appendChild( this.specific_y_group.appendChild(
make_y_line( makeYLine(
0, 0,
this.width, this.width,
this.width + 5, this.width + 5,
@ -407,8 +406,8 @@ export default class AxisChart extends BaseChart {
this.make_new_units_for_dataset( this.make_new_units_for_dataset(
this.x_axis_positions, this.x_axis_positions,
this.y_sums.map( val => float_2(this.zero_line - val * this.multiplier)), this.y_sums.map( val => floatTwo(this.zero_line - val * this.multiplier)),
'light-grey', '#f0f4f7',
0, 0,
1, 1,
this.sum_group, this.sum_group,
@ -482,17 +481,23 @@ export default class AxisChart extends BaseChart {
this.setup_x(); this.setup_x();
this.setup_y(); 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 // 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); this.make_x_axis(true);
setTimeout(() => { setTimeout(() => {
if(!this.updating) this.make_x_axis(); if(!this.updating) this.make_x_axis();
}, 350); }, 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 && (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); this.make_y_axis(true);
setTimeout(() => { setTimeout(() => {
@ -503,9 +508,6 @@ export default class AxisChart extends BaseChart {
}, 350); }, 350);
} }
// Change in data, so calculate dependencies
this.calc_y_dependencies();
this.animate_graphs(); this.animate_graphs();
// Trigger animation with the animatable elements in this.elements_to_animate // 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(d, i, old_x, old_y, new_x, new_y) {
// Animate path const newPointsList = new_y.map((y, i) => (new_x[i] + ',' + y));
const new_points_list = new_y.map((y, i) => (new_x[i] + ',' + y)); const newPathStr = newPointsList.join("L");
const new_path_str = new_points_list.join("L"); this.elements_to_animate = this.elements_to_animate
.concat(this.animator['path'](d, newPathStr));
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);
}
} }
animate_units(d, index, old_x, old_y, new_x, new_y) { animate_units(d, index, old_x, old_y, new_x, new_y) {
let type = this.unit_args.type; 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) => { d.svg_units.map((unit, i) => {
if(new_x[i] === undefined || new_y[i] === undefined) return; 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 {unit:unit, array:d.svg_units, index: i}, // unit, with info to replace where it came from in the data
new_x[i], new_x[i],
new_y[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]; const last_new_y_pos = new_y[new_y.length - 1];
if(this.no_of_extra_pts >= 0) { if(this.no_of_extra_pts >= 0) {
// First substitute current path with a squiggled one (looking the same but // First substitute current path with a squiggled one
// having more points at end), // (that looks the same but has more points at end),
// then animate to stretch it later to new points // then animate to stretch it later to new points
// (new points already have more points) // (new points already have more points)
@ -666,7 +651,7 @@ export default class AxisChart extends BaseChart {
if(typeof new_pos === 'string') { if(typeof new_pos === 'string') {
new_pos = parseInt(new_pos.substring(0, new_pos.length-1)); new_pos = parseInt(new_pos.substring(0, new_pos.length-1));
} }
const x_line = make_x_line( const x_line = makeXLine(
height, height,
text_start_at, text_start_at,
value, // new value 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 [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'; let axis_label_class = !specific ? 'y-value-text' : 'specific-value';
value = !specific ? value : (value+"").toUpperCase(); value = !specific ? value : (value+"").toUpperCase();
const y_line = make_y_line( const y_line = makeYLine(
start_at, start_at,
width, width,
text_end_at, text_end_at,
@ -837,7 +822,7 @@ export default class AxisChart extends BaseChart {
calc_y_dependencies() { calc_y_dependencies() {
this.y_min_tops = new Array(this.x_axis_positions.length).fill(9999); this.y_min_tops = new Array(this.x_axis_positions.length).fill(9999);
this.y.map(d => { 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) => { d.y_tops.map( (y_top, i) => {
if(y_top < this.y_min_tops[i]) { if(y_top < this.y_min_tops[i]) {
this.y_min_tops[i] = y_top; this.y_min_tops[i] = y_top;

View File

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

View File

@ -1,9 +1,31 @@
import SvgTip from '../objects/SvgTip'; import SvgTip from '../objects/SvgTip';
import $ from '../utils/dom'; import $ from '../utils/dom';
import { get_string_width } from '../utils/helpers'; import { makeSVGContainer, makeSVGDefs, makeSVGGroup } from '../utils/draw';
import { get_color } from '../utils/colors'; import { getStringWidth } from '../utils/helpers';
import { getColor, DEFAULT_COLORS } from '../utils/colors';
import Chart from '../charts'; 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 { export default class BaseChart {
constructor({ constructor({
height = 240, height = 240,
@ -16,7 +38,7 @@ export default class BaseChart {
is_navigable = 0, is_navigable = 0,
has_legend = 0, has_legend = 0,
type = '', // eslint-disable-line no-unused-vars type = '',
parent, parent,
data data
@ -37,55 +59,24 @@ export default class BaseChart {
this.current_index = 0; this.current_index = 0;
} }
this.has_legend = has_legend; 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); this.set_margins(height);
} }
get_different_chart(type) { get_different_chart(type) {
if(!this.chart_types.includes(type)) {
console.error(`'${type}' is not a valid chart type.`);
}
if(type === this.type) return; if(type === this.type) return;
// Only across compatible types if(!ALL_CHART_TYPES.includes(type)) {
let compatible_types = { console.error(`'${type}' is not a valid chart type.`);
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 if(!COMPATIBLE_CHARTS[this.type].includes(type)) {
let color_compatible_types = {
bar: ['line', 'scatter'],
line: ['scatter', 'bar'],
pie: ['percentage'],
scatter: ['line', 'bar'],
percentage: ['pie'],
heatmap: []
};
if(!compatible_types[this.type].includes(type)) {
console.error(`'${this.type}' chart cannot be converted to a '${type}' chart.`); console.error(`'${this.type}' chart cannot be converted to a '${type}' chart.`);
} }
// whether the new chart can use the existing colors // 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 // Okay, this is anticlimactic
// this function will need to actually be 'change_chart_type(type)' // 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) { set_margins(height) {
this.base_height = height; this.base_height = height;
this.height = height - 40; this.height = height - 40;
@ -155,7 +161,7 @@ export default class BaseChart {
let special_values_width = 0; let special_values_width = 0;
let char_width = 8; let char_width = 8;
this.specific_values.map(val => { 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) { if(str_width > special_values_width) {
special_values_width = str_width - 40; special_values_width = str_width - 40;
} }
@ -187,26 +193,22 @@ export default class BaseChart {
} }
make_chart_area() { make_chart_area() {
this.svg = $.createSVG('svg', { this.svg = makeSVGContainer(
className: 'chart', this.chart_wrapper,
inside: this.chart_wrapper, 'chart',
width: this.base_width, this.base_width,
height: this.base_height this.base_height
}); );
this.svg_defs = makeSVGDefs(this.svg);
this.svg_defs = $.createSVG('defs', {
inside: this.svg,
});
return this.svg; return this.svg;
} }
make_draw_area() { make_draw_area() {
this.draw_area = $.createSVG("g", { this.draw_area = makeSVGGroup(
className: this.type + '-chart', this.svg,
inside: this.svg, this.type + '-chart',
transform: `translate(${this.translate_x}, ${this.translate_y})` `translate(${this.translate_x}, ${this.translate_y})`
}); );
} }
setup_components() { } setup_components() { }
@ -295,4 +297,8 @@ export default class BaseChart {
// Objects // Objects
setup_utils() { } setup_utils() { }
makeDrawAreaComponent(className, transform='') {
return makeSVGGroup(this.draw_area, className, transform);
}
} }

View File

@ -1,8 +1,8 @@
import BaseChart from './BaseChart'; import BaseChart from './BaseChart';
import $ from '../utils/dom'; import { makeSVGGroup, makeHeatSquare, makeText } from '../utils/draw';
import { add_days, get_dd_mm_yyyy, get_weeks_between } from '../utils/date-utils'; import { addDays, getDdMmYyyy, getWeeksBetween } from '../utils/date-utils';
import { calc_distribution, get_max_checkpoint } from '../utils/intervals'; import { calcDistribution, getMaxCheckpoint } from '../utils/intervals';
import { is_valid_color } from '../utils/colors'; import { isValidColor } from '../utils/colors';
export default class Heatmap extends BaseChart { export default class Heatmap extends BaseChart {
constructor({ constructor({
@ -25,14 +25,14 @@ export default class Heatmap extends BaseChart {
this.count_label = count_label; this.count_label = count_label;
let today = new Date(); let today = new Date();
this.start = start || add_days(today, 365); this.start = start || addDays(today, 365);
legend_colors = legend_colors.slice(0, 5); legend_colors = legend_colors.slice(0, 5);
this.legend_colors = this.validate_colors(legend_colors) this.legend_colors = this.validate_colors(legend_colors)
? legend_colors ? legend_colors
: ['#ebedf0', '#c6e48b', '#7bc96f', '#239a3b', '#196127']; : ['#ebedf0', '#c6e48b', '#7bc96f', '#239a3b', '#196127'];
// Hardcoded for a fixed 5-color theme, // Fixed 5-color theme,
// More colors are difficult to parse visually // More colors are difficult to parse visually
this.distribution_size = 5; this.distribution_size = 5;
@ -45,7 +45,7 @@ export default class Heatmap extends BaseChart {
let valid = 1; let valid = 1;
colors.forEach(function(string) { colors.forEach(function(string) {
if(!is_valid_color(string)) { if(!isValidColor(string)) {
valid = 0; valid = 0;
console.warn('"' + string + '" is not a valid color.'); 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.first_week_start = new Date(this.start.toDateString());
this.last_week_start = new Date(this.today.toDateString()); this.last_week_start = new Date(this.today.toDateString());
if(this.first_week_start.getDay() !== 7) { 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) { 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() { set_width() {
@ -81,15 +81,13 @@ export default class Heatmap extends BaseChart {
} }
setup_components() { setup_components() {
this.domain_label_group = $.createSVG("g", { this.domain_label_group = this.makeDrawAreaComponent(
className: "domain-label-group chart-label", 'domain-label-group chart-label');
inside: this.draw_area
}); this.data_groups = this.makeDrawAreaComponent(
this.data_groups = $.createSVG("g", { 'data-groups',
className: "data-groups", `translate(0, 20)`
inside: this.draw_area, );
transform: `translate(0, 20)`
});
} }
setup_values() { setup_values() {
@ -97,7 +95,7 @@ export default class Heatmap extends BaseChart {
this.data_groups.textContent = ''; this.data_groups.textContent = '';
let data_values = Object.keys(this.data).map(key => this.data[key]); 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", this.month_names = ["January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December" "July", "August", "September", "October", "November", "December"
@ -129,7 +127,7 @@ export default class Heatmap extends BaseChart {
this.months.push(this.current_month + ''); this.months.push(this.current_month + '');
this.month_weeks[this.current_month] = 1; this.month_weeks[this.current_month] = 1;
} }
add_days(current_week_sunday, 7); addDays(current_week_sunday, 7);
} }
this.render_month_labels(); this.render_month_labels();
} }
@ -144,10 +142,7 @@ export default class Heatmap extends BaseChart {
let month_change = 0; let month_change = 0;
let week_col_change = 0; let week_col_change = 0;
let data_group = $.createSVG("g", { let data_group = makeSVGGroup(this.data_groups, 'data-group');
className: "data-group",
inside: this.data_groups
});
for(var y = 0, i = 0; i < no_of_weekdays; i += step, y += (square_side + cell_padding)) { for(var y = 0, i = 0; i < no_of_weekdays; i += step, y += (square_side + cell_padding)) {
let data_value = 0; let data_value = 0;
@ -165,26 +160,23 @@ export default class Heatmap extends BaseChart {
} }
if(data_value) { 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; let x = 13 + (index + week_col_change) * 12;
$.createSVG("rect", { let dataAttr = {
className: 'day', 'data-date': getDdMmYyyy(current_date),
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),
'data-value': data_value, 'data-value': data_value,
'data-day': current_date.getDay() '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); let next_date = new Date(current_date);
add_days(next_date, 1); addDays(next_date, 1);
if(next_date.getTime() > today_time) break; if(next_date.getTime() > today_time) break;
@ -224,15 +216,8 @@ export default class Heatmap extends BaseChart {
this.month_start_points.map((start, i) => { this.month_start_points.map((start, i) => {
let month_name = this.month_names[this.months[i]].substring(0, 3); let month_name = this.month_names[this.months[i]].substring(0, 3);
let text = makeText('y-value-text', start+12, 10, month_name);
$.createSVG('text', { this.domain_label_group.appendChild(text);
className: 'y-value-text',
inside: this.domain_label_group,
x: start + 12,
y: 10,
dy: '.32em',
innerHTML: month_name
});
}); });
} }

View File

@ -1,5 +1,5 @@
import AxisChart from './AxisChart'; import AxisChart from './AxisChart';
import $ from '../utils/dom'; import { makeSVGGroup, makePath, makeGradient } from '../utils/draw';
export default class LineChart extends AxisChart { export default class LineChart extends AxisChart {
constructor(args) { constructor(args) {
@ -33,10 +33,10 @@ export default class LineChart extends AxisChart {
setup_path_groups() { setup_path_groups() {
this.paths_groups = []; this.paths_groups = [];
this.y.map((d, i) => { this.y.map((d, i) => {
this.paths_groups[i] = $.createSVG('g', { this.paths_groups[i] = makeSVGGroup(
className: 'path-group path-group-' + i, this.draw_area,
inside: this.draw_area 'path-group path-group-' + i
}); );
}); });
} }
@ -68,14 +68,11 @@ export default class LineChart extends AxisChart {
this.paths_groups[i].textContent = ''; this.paths_groups[i].textContent = '';
d.path = $.createSVG('path', { d.path = makePath("M"+points_str, 'line-graph-path', color);
inside: this.paths_groups[i], this.paths_groups[i].appendChild(d.path);
style: `stroke: ${color}`,
d: "M"+points_str
});
if(this.heatline) { 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})`; 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) { fill_region_for_dataset(d, i, color, points_str) {
let gradient_id = this.make_gradient(color, true); 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}`;
d.region_path = $.createSVG('path', { d.regionPath = makePath(pathStr, `region-fill`, 'none', `url(#${gradient_id})`);
inside: this.paths_groups[i], this.paths_groups[i].appendChild(d.regionPath);
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]);
return gradient_id;
} }
} }

View File

@ -1,7 +1,8 @@
import BaseChart from './BaseChart'; import BaseChart from './BaseChart';
import $ from '../utils/dom'; import $ from '../utils/dom';
import { lighten_darken_color } from '../utils/colors'; import { makePath } from '../utils/draw';
import { runSVGAnimation, transform } from '../utils/animate'; import { lightenDarkenColor } from '../utils/colors';
import { runSVGAnimation, transform } from '../utils/animation';
const ANGLE_RATIO = Math.PI / 180; const ANGLE_RATIO = Math.PI / 180;
const FULL_ANGLE = 360; const FULL_ANGLE = 360;
@ -93,13 +94,10 @@ export default class PieChart extends BaseChart {
curEnd = endPosition; curEnd = endPosition;
} }
const curPath = this.makeArcPath(curStart,curEnd); const curPath = this.makeArcPath(curStart,curEnd);
let slice = $.createSVG('path',{ let slice = makePath(curPath, 'pie-path', 'none', this.colors[i]);
inside: this.draw_area, slice.style.transition = 'transform .3s;';
className: 'pie-path', this.draw_area.appendChild(slice);
style: 'transition:transform .3s;',
d: curPath,
fill: this.colors[i]
});
this.slices.push(slice); this.slices.push(slice);
this.slicesProperties.push({ this.slicesProperties.push({
startPosition, startPosition,
@ -155,7 +153,7 @@ export default class PieChart extends BaseChart {
const color = this.colors[i]; const color = this.colors[i];
if(flag){ if(flag){
transform(path,this.calTranslateByAngle(this.slicesProperties[i])); 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 g_off = $.offset(this.svg);
let x = e.pageX - g_off.left + 10; let x = e.pageX - g_off.left + 10;
let y = e.pageY - g_off.top - 10; let y = e.pageY - g_off.top - 10;
@ -167,7 +165,7 @@ export default class PieChart extends BaseChart {
}else{ }else{
transform(path,'translate3d(0,0,0)'); transform(path,'translate3d(0,0,0)');
this.tip.hide_tip(); this.tip.hide_tip();
path.setAttribute('fill',color); path.style.fill = color;
} }
} }

View File

@ -1,101 +1,58 @@
// Leveraging SMIL Animations import { getBarHeightAndYAttr } from './draw-utils';
const EASING = { export function getAnimXLine() {}
ease: "0.25 0.1 0.25 1",
linear: "0 0 1 1", export function getAnimYLine() {}
// easein: "0.42 0 1 1",
easein: "0.1 0.8 0.2 1", export var Animator = (function() {
easeout: "0 0 0.58 1", var Animator = function(totalHeight, totalWidth, zeroLine, avgUnitWidth) {
easeinout: "0.42 0 0.58 1" // constants
this.totalHeight = totalHeight;
this.totalWidth = totalWidth;
// changeables
this.avgUnitWidth = avgUnitWidth;
this.zeroLine = zeroLine;
}; };
function animateSVG(element, props, dur, easing_type="linear", type=undefined, old_values={}) { 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);
let anim_element = element.cloneNode(true); x = start + (width * index);
let new_element = element.cloneNode(true);
for(var attributeName in props) { return [barObj, {width: width, height: height, x: x, y: y}, 350, "easein"];
let animate_element; // bar.animate({height: args.newHeight, y: yTop}, 350, mina.easein);
if(attributeName === 'transform') { },
animate_element = document.createElementNS("http://www.w3.org/2000/svg", "animateTransform");
} else { dot: function(dotObj, x, yTop) {
animate_element = document.createElementNS("http://www.w3.org/2000/svg", "animate"); 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);
} }
let current_value = old_values[attributeName] || element.getAttribute(attributeName);
let value = props[attributeName];
let anim_attr = { return pathComponents;
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) { return Animator;
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,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;
}

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; if (r > 255) return 255;
else if (r < 0) return 0; else if (r < 0) return 0;
return r; return r;
} }
export function lighten_darken_color(color, amt) { export function lightenDarkenColor(color, amt) {
let col = get_color(color); let col = getColor(color);
let usePound = false; let usePound = false;
if (col[0] == "#") { if (col[0] == "#") {
col = col.slice(1); col = col.slice(1);
usePound = true; usePound = true;
} }
let num = parseInt(col,16); let num = parseInt(col,16);
let r = limit_color((num >> 16) + amt); let r = limitColor((num >> 16) + amt);
let b = limit_color(((num >> 8) & 0x00FF) + amt); let b = limitColor(((num >> 8) & 0x00FF) + amt);
let g = limit_color((num & 0x0000FF) + amt); let g = limitColor((num & 0x0000FF) + amt);
return (usePound?"#":"") + (g | (b << 8) | (r << 16)).toString(16); 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 // https://stackoverflow.com/a/8027444/6495043
return /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(string); return /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(string);
} }
export const color_map = { export const getColor = (color) => {
'light-blue': '#7cd6fd', return PRESET_COLOR_MAP[color] || color;
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;
}; };

View File

@ -1,13 +1,13 @@
// Playing around with dates // Playing around with dates
// https://stackoverflow.com/a/11252167/6495043 // https://stackoverflow.com/a/11252167/6495043
function treat_as_utc(date_str) { function treatAsUtc(dateStr) {
let result = new Date(date_str); let result = new Date(dateStr);
result.setMinutes(result.getMinutes() - result.getTimezoneOffset()); result.setMinutes(result.getMinutes() - result.getTimezoneOffset());
return result; return result;
} }
export function get_dd_mm_yyyy(date) { export function getDdMmYyyy(date) {
let dd = date.getDate(); let dd = date.getDate();
let mm = date.getMonth() + 1; // getMonth() is zero-based let mm = date.getMonth() + 1; // getMonth() is zero-based
return [ return [
@ -17,18 +17,18 @@ export function get_dd_mm_yyyy(date) {
].join('-'); ].join('-');
} }
export function get_weeks_between(start_date_str, end_date_str) { export function getWeeksBetween(startDateStr, endDateStr) {
return Math.ceil(get_days_between(start_date_str, end_date_str) / 7); return Math.ceil(getDaysBetween(startDateStr, endDateStr) / 7);
} }
export function get_days_between(start_date_str, end_date_str) { export function getDaysBetween(startDateStr, endDateStr) {
let milliseconds_per_day = 24 * 60 * 60 * 1000; let millisecondsPerDay = 24 * 60 * 60 * 1000;
return (treat_as_utc(end_date_str) - treat_as_utc(start_date_str)) / milliseconds_per_day; return (treatAsUtc(endDateStr) - treatAsUtc(startDateStr)) / millisecondsPerDay;
} }
// mutates // mutates
export function add_days(date, number_of_days) { export function addDays(date, numberOfDays) {
date.setDate(date.getDate() + number_of_days); date.setDate(date.getDate() + numberOfDays);
} }
// export function get_month_name() {} // export function getMonthName() {}

View File

@ -25,6 +25,7 @@ $.create = (tag, o) => {
var ref = $(val); var ref = $(val);
ref.parentNode.insertBefore(element, ref); ref.parentNode.insertBefore(element, ref);
element.appendChild(ref); element.appendChild(ref);
} else if (i === "styles") { } else if (i === "styles") {
if(typeof val === "object") { if(typeof val === "object") {
Object.keys(val).map(prop => { Object.keys(val).map(prop => {
@ -42,33 +43,6 @@ $.create = (tag, o) => {
return element; 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) => { $.offset = (element) => {
let rect = element.getBoundingClientRect(); let rect = element.getBoundingClientRect();
return { return {

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

View File

@ -1,135 +1,187 @@
import $ from './dom'; import { getBarHeightAndYAttr } from './draw-utils';
export var UnitRenderer = (function() { // Constants used
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) { function $(expr, con) {
let height, y; return typeof expr === "string"? (con || document).querySelector(expr) : expr || null;
if (y_top <= zero_line) { }
height = zero_line - y_top;
y = y_top;
// In case of invisible bars function createSVG(tag, o) {
if(height === 0) { var element = document.createElementNS("http://www.w3.org/2000/svg", tag);
height = total_height * 0.01;
y -= height; 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 === "styles") {
if(typeof val === "object") {
Object.keys(val).map(prop => {
element.style[prop] = val[prop];
});
} }
} else { } else {
height = y_top - zero_line; if(i === "className") { i = "class"; }
y = zero_line; if(i === "innerHTML") {
element['textContent'] = val;
// In case of invisible bars } else {
if(height === 0) { element.setAttribute(i, val);
height = total_height * 0.01; }
} }
} }
return [height, y]; return element;
} }
UnitRenderer.prototype = { function renderVerticalGradient(svgDefElem, gradientId) {
draw_bar: function (x, y_top, args, color, index, dataset_index, no_of_datasets) { return createSVG('linearGradient', {
let total_width = this.avg_unit_width - args.space_width; inside: svgDefElem,
let start_x = x - total_width/2; id: gradientId,
x1: 0,
x2: 0,
y1: 0,
y2: 1
});
}
let width = total_width / no_of_datasets; function setGradientStop(gradElem, offset, color, opacity) {
let current_x = start_x + width * dataset_index; return createSVG('stop', {
'inside': gradElem,
'style': `stop-color: ${color}`,
'offset': offset,
'stop-opacity': opacity
});
}
let [height, y] = get_bar_height_and_y_attr(y_top, this.zero_line, this.total_height); export function makeSVGContainer(parent, className, width, height) {
return createSVG('svg', {
return $.createSVG('rect', { className: className,
className: `bar mini`, inside: parent,
style: `fill: ${color}`,
'data-point-index': index,
x: current_x,
y: y,
width: width, width: width,
height: height 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
});
},
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);
} }
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
}
});
}
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];
}
setGradientStop(gradientDef, "0%", color, opacities[0]);
setGradientStop(gradientDef, "50%", color, opacities[1]);
setGradientStop(gradientDef, "100%", color, opacities[2]);
return gradientId;
}
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 make_x_line(height, text_start_at, point, label_class, axis_line_class, x_pos) { export function makeText(className, x, y, content) {
let line = $.createSVG('line', { return createSVG('text', {
className: className,
x: x,
y: y,
dy: '.32em',
innerHTML: content
});
}
export function makeXLine(height, textStartAt, point, labelClass, axisLineClass, xPos) {
let line = createSVG('line', {
x1: 0, x1: 0,
x2: 0, x2: 0,
y1: 0, y1: 0,
y2: height y2: height
}); });
let text = $.createSVG('text', { let text = createSVG('text', {
className: label_class, className: labelClass,
x: 0, x: 0,
y: text_start_at, y: textStartAt,
dy: '.71em', dy: '.71em',
innerHTML: point innerHTML: point
}); });
let x_line = $.createSVG('g', { let xLine = createSVG('g', {
className: `tick ${axis_line_class}`, className: `tick ${axisLineClass}`,
transform: `translate(${ x_pos }, 0)` transform: `translate(${ xPos }, 0)`
}); });
x_line.appendChild(line); xLine.appendChild(line);
x_line.appendChild(text); 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="") { export function makeYLine(startAt, width, textEndAt, point, labelClass, axisLineClass, yPos, darker=false, lineType="") {
let line = $.createSVG('line', { let line = createSVG('line', {
className: line_type === "dashed" ? "dashed": "", className: lineType === "dashed" ? "dashed": "",
x1: start_at, x1: startAt,
x2: width, x2: width,
y1: 0, y1: 0,
y2: 0 y2: 0
}); });
let text = $.createSVG('text', { let text = createSVG('text', {
className: label_class, className: labelClass,
x: text_end_at, x: textEndAt,
y: 0, y: 0,
dy: '.32em', dy: '.32em',
innerHTML: point+"" innerHTML: point+""
}); });
let y_line = $.createSVG('g', { let yLine = createSVG('g', {
className: `tick ${axis_line_class}`, className: `tick ${axisLineClass}`,
transform: `translate(0, ${y_pos})`, transform: `translate(0, ${yPos})`,
'stroke-opacity': 1 '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)"; line.style.stroke = "rgba(27, 31, 35, 0.6)";
} }
y_line.appendChild(line); yLine.appendChild(line);
y_line.appendChild(text); 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;
};
export function get_anim_y_line() {} 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;
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;
})();

View File

@ -2,7 +2,7 @@
* Returns the value of a number upto 2 decimal places. * Returns the value of a number upto 2 decimal places.
* @param {Number} d Any number * @param {Number} d Any number
*/ */
export function float_2(d) { export function floatTwo(d) {
return parseFloat(d.toFixed(2)); return parseFloat(d.toFixed(2));
} }
@ -11,13 +11,13 @@ export function float_2(d) {
* @param {Array} arr1 First array * @param {Array} arr1 First array
* @param {Array} arr2 Second array * @param {Array} arr2 Second array
*/ */
export function arrays_equal(arr1, arr2) { export function arraysEqual(arr1, arr2) {
if(arr1.length !== arr2.length) return false; if(arr1.length !== arr2.length) return false;
let are_equal = true; let areEqual = true;
arr1.map((d, i) => { 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. * Returns pixel width of string.
* @param {String} 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) { export function getStringWidth(string, charWidth) {
return (string+"").length * char_width; return (string+"").length * charWidth;
} }

View File

@ -21,77 +21,77 @@ function normalize(x) {
return [sig * man, exp]; return [sig * man, exp];
} }
function get_range_intervals(max, min=0) { function getRangeIntervals(max, min=0) {
let upper_bound = Math.ceil(max); let upperBound = Math.ceil(max);
let lower_bound = Math.floor(min); let lowerBound = Math.floor(min);
let range = upper_bound - lower_bound; let range = upperBound - lowerBound;
let no_of_parts = range; let noOfParts = range;
let part_size = 1; let partSize = 1;
// To avoid too many partitions // To avoid too many partitions
if(range > 5) { if(range > 5) {
if(range % 2 !== 0) { if(range % 2 !== 0) {
upper_bound++; upperBound++;
// Recalc range // Recalc range
range = upper_bound - lower_bound; range = upperBound - lowerBound;
} }
no_of_parts = range/2; noOfParts = range/2;
part_size = 2; partSize = 2;
} }
// Special case: 1 and 2 // Special case: 1 and 2
if(range <= 2) { if(range <= 2) {
no_of_parts = 4; noOfParts = 4;
part_size = range/no_of_parts; partSize = range/noOfParts;
} }
// Special case: 0 // Special case: 0
if(range === 0) { if(range === 0) {
no_of_parts = 5; noOfParts = 5;
part_size = 1; partSize = 1;
} }
let intervals = []; let intervals = [];
for(var i = 0; i <= no_of_parts; i++){ for(var i = 0; i <= noOfParts; i++){
intervals.push(lower_bound + part_size * i); intervals.push(lowerBound + partSize * i);
} }
return intervals; return intervals;
} }
function get_intervals(max_value, min_value=0) { function getIntervals(maxValue, minValue=0) {
let [normal_max_value, exponent] = normalize(max_value); let [normalMaxValue, exponent] = normalize(maxValue);
let normal_min_value = min_value ? min_value/Math.pow(10, exponent): 0; let normalMinValue = minValue ? minValue/Math.pow(10, exponent): 0;
// Allow only 7 significant digits // 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)); intervals = intervals.map(value => value * Math.pow(10, exponent));
return intervals; return intervals;
} }
export function calc_intervals(values, with_minimum=false) { export function calcIntervals(values, withMinimum=false) {
//*** Where the magic happens *** //*** Where the magic happens ***
// Calculates best-fit y intervals from given values // Calculates best-fit y intervals from given values
// and returns the interval array // and returns the interval array
let max_value = Math.max(...values); let maxValue = Math.max(...values);
let min_value = Math.min(...values); let minValue = Math.min(...values);
// Exponent to be used for pretty print // Exponent to be used for pretty print
let exponent = 0, intervals = []; // eslint-disable-line no-unused-vars let exponent = 0, intervals = []; // eslint-disable-line no-unused-vars
function get_positive_first_intervals(max_value, abs_min_value) { function getPositiveFirstIntervals(maxValue, absMinValue) {
let intervals = get_intervals(max_value); let intervals = getIntervals(maxValue);
let interval_size = intervals[1] - intervals[0]; let intervalSize = intervals[1] - intervals[0];
// Then unshift the negative values // Then unshift the negative values
let value = 0; let value = 0;
for(var i = 1; value < abs_min_value; i++) { for(var i = 1; value < absMinValue; i++) {
value += interval_size; value += intervalSize;
intervals.unshift((-1) * value); intervals.unshift((-1) * value);
} }
return intervals; return intervals;
@ -99,52 +99,52 @@ export function calc_intervals(values, with_minimum=false) {
// CASE I: Both non-negative // CASE I: Both non-negative
if(max_value >= 0 && min_value >= 0) { if(maxValue >= 0 && minValue >= 0) {
exponent = normalize(max_value)[1]; exponent = normalize(maxValue)[1];
if(!with_minimum) { if(!withMinimum) {
intervals = get_intervals(max_value); intervals = getIntervals(maxValue);
} else { } 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) { else if(maxValue > 0 && minValue < 0) {
// `with_minimum` irrelevant in this case, // `withMinimum` irrelevant in this case,
// We'll be handling both sides of zero separately // We'll be handling both sides of zero separately
// (both starting from zero) // (both starting from zero)
// Because ceil() and floor() behave differently // Because ceil() and floor() behave differently
// in those two regions // in those two regions
let abs_min_value = Math.abs(min_value); let absMinValue = Math.abs(minValue);
if(max_value >= abs_min_value) { if(maxValue >= absMinValue) {
exponent = normalize(max_value)[1]; exponent = normalize(maxValue)[1];
intervals = get_positive_first_intervals(max_value, abs_min_value); intervals = getPositiveFirstIntervals(maxValue, absMinValue);
} else { } else {
// Mirror: max_value => abs_min_value, then change sign // Mirror: maxValue => absMinValue, then change sign
exponent = normalize(abs_min_value)[1]; exponent = normalize(absMinValue)[1];
let pos_intervals = get_positive_first_intervals(abs_min_value, max_value); let posIntervals = getPositiveFirstIntervals(absMinValue, maxValue);
intervals = pos_intervals.map(d => d * (-1)); intervals = posIntervals.map(d => d * (-1));
} }
} }
// CASE III: Both non-positive // CASE III: Both non-positive
else if(max_value <= 0 && min_value <= 0) { else if(maxValue <= 0 && minValue <= 0) {
// Mirrored Case I: // Mirrored Case I:
// Work with positives, then reverse the sign and array // Work with positives, then reverse the sign and array
let pseudo_max_value = Math.abs(min_value); let pseudoMaxValue = Math.abs(minValue);
let pseudo_min_value = Math.abs(max_value); let pseudoMinValue = Math.abs(maxValue);
exponent = normalize(pseudo_max_value)[1]; exponent = normalize(pseudoMaxValue)[1];
if(!with_minimum) { if(!withMinimum) {
intervals = get_intervals(pseudo_max_value); intervals = getIntervals(pseudoMaxValue);
} else { } else {
intervals = get_intervals(pseudo_max_value, pseudo_min_value); intervals = getIntervals(pseudoMaxValue, pseudoMinValue);
} }
intervals = intervals.reverse().map(d => d * (-1)); intervals = intervals.reverse().map(d => d * (-1));
@ -153,23 +153,23 @@ export function calc_intervals(values, with_minimum=false) {
return intervals; return intervals;
} }
export function calc_distribution(values, distribution_size) { export function calcDistribution(values, distributionSize) {
// Assume non-negative values, // Assume non-negative values,
// implying distribution minimum at zero // 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 = []; let distribution = [];
for(var i = 0; i < distribution_size; i++) { for(var i = 0; i < distributionSize; i++) {
let checkpoint = data_max_value * (distribution_step * i); let checkpoint = dataMaxValue * (distributionStep * i);
distribution.push(checkpoint); distribution.push(checkpoint);
} }
return distribution; return distribution;
} }
export function get_max_checkpoint(value, distribution) { export function getMaxCheckpoint(value, distribution) {
return distribution.filter(d => d < value).length; return distribution.filter(d => d < value).length;
} }

View File

@ -45,8 +45,6 @@
color: #98d85b; color: #98d85b;
} }
} }
} }
.axis, .chart-label { .axis, .chart-label {
font-size: 11px; font-size: 11px;