浏览代码

[major] more modules: date-utils, intervals

tags/1.2.0
pratu16x7 7 年前
父节点
当前提交
0f0e371006
共有 12 个文件被更改,包括 841 次插入938 次删除
  1. +304
    -342
      dist/frappe-charts.min.cjs.js
  2. +304
    -342
      dist/frappe-charts.min.esm.js
  3. +1
    -1
      dist/frappe-charts.min.iife.js
  4. +1
    -1
      docs/assets/js/frappe-charts.min.js
  5. +13
    -187
      src/scripts/charts/AxisChart.js
  6. +9
    -3
      src/scripts/charts/BaseChart.js
  7. +8
    -42
      src/scripts/charts/Heatmap.js
  8. +0
    -9
      src/scripts/charts/PercentageChart.js
  9. +0
    -1
      src/scripts/charts/PieChart.js
  10. +34
    -0
      src/scripts/utils/date-utils.js
  11. +54
    -8
      src/scripts/utils/draw.js
  12. +113
    -2
      src/scripts/utils/intervals.js

+ 304
- 342
dist/frappe-charts.min.cjs.js
文件差异内容过多而无法显示
查看文件


+ 304
- 342
dist/frappe-charts.min.esm.js
文件差异内容过多而无法显示
查看文件


+ 1
- 1
dist/frappe-charts.min.iife.js
文件差异内容过多而无法显示
查看文件


+ 1
- 1
docs/assets/js/frappe-charts.min.js
文件差异内容过多而无法显示
查看文件


+ 13
- 187
src/scripts/charts/AxisChart.js 查看文件

@@ -1,6 +1,7 @@
import $ from '../utils/dom';
import { UnitRenderer } from '../utils/draw';
import { UnitRenderer, make_x_line, make_y_line } from '../utils/draw';
import { runSVGAnimation } from '../utils/animate';
import { calc_y_intervals } from '../utils/intervals';
import { float_2, arrays_equal, get_string_width } from '../utils/helpers';
import BaseChart from './BaseChart';

@@ -17,9 +18,6 @@ export default class AxisChart extends BaseChart {
this.get_y_tooltip = this.format_lambdas.y_tooltip;
this.get_x_tooltip = this.format_lambdas.x_tooltip;

this.colors = ['green', 'blue', 'violet', 'red', 'orange',
'yellow', 'light-blue', 'light-green', 'purple', 'magenta'];

this.zero_line = this.height;

this.old_values = {};
@@ -58,7 +56,7 @@ export default class AxisChart extends BaseChart {
values = values.concat(this.y_sums);
}

this.y_axis_values = this.get_y_axis_points(values);
this.y_axis_values = calc_y_intervals(values);

if(!this.y_old_axis_values) {
this.y_old_axis_values = this.y_axis_values.slice();
@@ -158,7 +156,7 @@ export default class AxisChart extends BaseChart {
}
}
this.x_axis_group.appendChild(
this.make_x_line(
make_x_line(
height,
text_start_at,
point,
@@ -183,7 +181,7 @@ export default class AxisChart extends BaseChart {
this.y_axis_group.textContent = '';
this.y_axis_values.map((value, i) => {
this.y_axis_group.appendChild(
this.make_y_line(
make_y_line(
start_at,
width,
text_end_at,
@@ -282,8 +280,10 @@ export default class AxisChart extends BaseChart {
units_group.textContent = '';
units_array.length = 0;

let unit_renderer = new UnitRenderer(this.height, this.zero_line, this.avg_unit_width);

y_values.map((y, i) => {
let data_unit = this.UnitRenderer['draw_' + unit.type](
let data_unit = unit_renderer['draw_' + unit.type](
x_values[i],
y,
unit.args,
@@ -305,7 +305,7 @@ export default class AxisChart extends BaseChart {
this.specific_y_group.textContent = '';
this.specific_values.map(d => {
this.specific_y_group.appendChild(
this.make_y_line(
make_y_line(
0,
this.width,
this.width + 5,
@@ -572,10 +572,11 @@ export default class AxisChart extends BaseChart {

animate_units(d, index, old_x, old_y, new_x, new_y) {
let type = this.unit_args.type;
let unit_renderer = new UnitRenderer(this.height, this.zero_line, this.avg_unit_width);

d.svg_units.map((unit, i) => {
if(new_x[i] === undefined || new_y[i] === undefined) return;
this.elements_to_animate.push(this.UnitRenderer['animate_' + type](
this.elements_to_animate.push(unit_renderer['animate_' + type](
{unit:unit, array:d.svg_units, index: i}, // unit, with info to replace where it came from in the data
new_x[i],
new_y[i],
@@ -639,7 +640,7 @@ export default class AxisChart extends BaseChart {
if(typeof new_pos === 'string') {
new_pos = parseInt(new_pos.substring(0, new_pos.length-1));
}
const x_line = this.make_x_line(
const x_line = make_x_line(
height,
text_start_at,
value, // new value
@@ -748,66 +749,6 @@ export default class AxisChart extends BaseChart {
}
}

make_x_line(height, text_start_at, point, label_class, axis_line_class, x_pos) {
let line = $.createSVG('line', {
x1: 0,
x2: 0,
y1: 0,
y2: height
});

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

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

x_level.appendChild(line);
x_level.appendChild(text);

return x_level;
}

make_y_line(start_at, width, text_end_at, point, label_class, axis_line_class, y_pos, darker=false, line_type="") {
let line = $.createSVG('line', {
className: line_type === "dashed" ? "dashed": "",
x1: start_at,
x2: width,
y1: 0,
y2: 0
});

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

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

if(darker) {
line.style.stroke = "rgba(27, 31, 35, 0.6)";
}

y_level.appendChild(line);
y_level.appendChild(text);

return y_level;
}

add_and_animate_y_line(value, old_pos, new_pos, i, group, type, specific=false) {
let filler = false;
if(typeof new_pos === 'string') {
@@ -825,7 +766,7 @@ export default class AxisChart extends BaseChart {
let [width, text_end_at, axis_line_class, start_at] = this.get_y_axis_line_props(specific);
let axis_label_class = !specific ? 'y-value-text' : 'specific-value';
value = !specific ? value : (value+"").toUpperCase();
const y_line = this.make_y_line(
const y_line = make_y_line(
start_at,
width,
text_end_at,
@@ -849,117 +790,6 @@ export default class AxisChart extends BaseChart {
]);
}

get_y_axis_points(array) {
//*** Where the magic happens ***

// Calculates best-fit y intervals from given values
// and returns the interval array

// TODO: Fractions

let max_bound, min_bound, pos_no_of_parts, neg_no_of_parts, part_size; // eslint-disable-line no-unused-vars

// Critical values
let max_val = parseInt(Math.max(...array));
let min_val = parseInt(Math.min(...array));
if(min_val >= 0) {
min_val = 0;
}

let get_params = (value1, value2) => {
let bound1, bound2, no_of_parts_1, no_of_parts_2, interval_size;
if((value1+"").length <= 1) {
[bound1, no_of_parts_1] = [10, 5];
} else {
[bound1, no_of_parts_1] = this.calc_upper_bound_and_no_of_parts(value1);
}

interval_size = bound1 / no_of_parts_1;
no_of_parts_2 = this.calc_no_of_parts(value2, interval_size);
bound2 = no_of_parts_2 * interval_size;

return [bound1, bound2, no_of_parts_1, no_of_parts_2, interval_size];
};

const abs_min_val = min_val * -1;
if(abs_min_val <= max_val) {
// Get the positive region intervals
// then calc negative ones accordingly
[max_bound, min_bound, pos_no_of_parts, neg_no_of_parts, part_size]
= get_params(max_val, abs_min_val);
if(abs_min_val === 0) {
min_bound = 0; neg_no_of_parts = 0;
}
} else {
// Get the negative region here first
[min_bound, max_bound, neg_no_of_parts, pos_no_of_parts, part_size]
= get_params(abs_min_val, max_val);
}

// Make both region parts even
if(pos_no_of_parts % 2 !== 0 && neg_no_of_parts > 0) pos_no_of_parts++;
if(neg_no_of_parts % 2 !== 0) {
// every increase in no_of_parts entails an increase in corresponding bound
// except here, it happens implicitly after every calc_no_of_parts() call
neg_no_of_parts++;
min_bound += part_size;
}

let no_of_parts = pos_no_of_parts + neg_no_of_parts;
if(no_of_parts > 5) {
no_of_parts /= 2;
part_size *= 2;

pos_no_of_parts /=2;
}

if (max_val < (pos_no_of_parts - 1) * part_size) {
no_of_parts--;
}

return this.get_intervals(
(-1) * min_bound,
part_size,
no_of_parts
);
}

get_intervals(start, interval_size, count) {
let intervals = [];
for(var i = 0; i <= count; i++){
intervals.push(start);
start += interval_size;
}
return intervals;
}

calc_upper_bound_and_no_of_parts(max_val) {
// Given a positive value, calculates a nice-number upper bound
// and a consequent optimal number of parts

const part_size = Math.pow(10, ((max_val+"").length - 1));
const no_of_parts = this.calc_no_of_parts(max_val, part_size);

// Use it to get a nice even upper bound
const upper_bound = part_size * no_of_parts;

return [upper_bound, no_of_parts];
}

calc_no_of_parts(value, divisor) {
// value should be a positive number, divisor should be greater than 0
// returns an even no of parts
let no_of_parts = Math.ceil(value / divisor);
if(no_of_parts % 2 !== 0) no_of_parts++; // Make it an even number

return no_of_parts;
}

get_optimal_no_of_parts(no_of_parts) {
// aka Divide by 2 if too large
return (no_of_parts < 5) ? no_of_parts : no_of_parts / 2;
}

set_avg_unit_width_and_x_offset() {
// Set the ... you get it
this.avg_unit_width = this.width/(this.x.length - 1);
@@ -991,8 +821,4 @@ export default class AxisChart extends BaseChart {
// this.chart_wrapper.removeChild(this.tip.container);
// this.make_tooltip();
}

setup_utils() {
this.UnitRenderer = new UnitRenderer(this.height, this.zero_line, this.avg_unit_width);
}
}

+ 9
- 3
src/scripts/charts/BaseChart.js 查看文件

@@ -7,10 +7,10 @@ export default class BaseChart {
constructor({
height = 240,

title = '', subtitle = '',

title = '',
subtitle = '',
colors = [],
format_lambdas = {},

summary = [],

is_navigable = 0,
@@ -39,6 +39,12 @@ export default class BaseChart {
}
this.has_legend = has_legend;

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

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

this.set_margins(height);


+ 8
- 42
src/scripts/charts/Heatmap.js 查看文件

@@ -1,5 +1,6 @@
import BaseChart from './BaseChart';
import $ from '../utils/dom';
import { add_days, get_dd_mm_yyyy, get_weeks_between } from '../utils/date-utils';

export default class Heatmap extends BaseChart {
constructor({
@@ -21,7 +22,7 @@ export default class Heatmap extends BaseChart {
this.count_label = count_label;

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

this.legend_colors = ['#ebedf0', '#c6e48b', '#7bc96f', '#239a3b', '#196127'];

@@ -39,12 +40,12 @@ export default class Heatmap extends BaseChart {
this.first_week_start = new Date(this.start.toDateString());
this.last_week_start = new Date(this.today.toDateString());
if(this.first_week_start.getDay() !== 7) {
this.add_days(this.first_week_start, (-1) * this.first_week_start.getDay());
add_days(this.first_week_start, (-1) * this.first_week_start.getDay());
}
if(this.last_week_start.getDay() !== 7) {
this.add_days(this.last_week_start, (-1) * this.last_week_start.getDay());
add_days(this.last_week_start, (-1) * this.last_week_start.getDay());
}
this.no_of_cols = this.get_weeks_between(this.first_week_start + '', this.last_week_start + '') + 1;
this.no_of_cols = get_weeks_between(this.first_week_start + '', this.last_week_start + '') + 1;
}

set_width() {
@@ -101,7 +102,7 @@ export default class Heatmap extends BaseChart {
this.months.push(this.current_month + '');
this.month_weeks[this.current_month] = 1;
}
this.add_days(current_week_sunday, 7);
add_days(current_week_sunday, 7);
}
this.render_month_labels();
}
@@ -148,13 +149,13 @@ export default class Heatmap extends BaseChart {
width: square_side,
height: square_side,
fill: this.legend_colors[color_index],
'data-date': this.get_dd_mm_yyyy(current_date),
'data-date': get_dd_mm_yyyy(current_date),
'data-value': data_value,
'data-day': current_date.getDay()
});

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


@@ -270,39 +271,4 @@ export default class Heatmap extends BaseChart {
return d <= value;
}).length - 1;
}

// TODO: date utils, move these out

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

get_dd_mm_yyyy(date) {
let dd = date.getDate();
let mm = date.getMonth() + 1; // getMonth() is zero-based
return [
(dd>9 ? '' : '0') + dd,
(mm>9 ? '' : '0') + mm,
date.getFullYear()
].join('-');
}

get_weeks_between(start_date_str, end_date_str) {
return Math.ceil(this.get_days_between(start_date_str, end_date_str) / 7);
}

get_days_between(start_date_str, end_date_str) {
let milliseconds_per_day = 24 * 60 * 60 * 1000;
return (this.treat_as_utc(end_date_str) - this.treat_as_utc(start_date_str)) / milliseconds_per_day;
}

// mutates
add_days(date, number_of_days) {
date.setDate(date.getDate() + number_of_days);
}

get_month_name() {}
}

+ 0
- 9
src/scripts/charts/PercentageChart.js 查看文件

@@ -13,13 +13,6 @@ export default class PercentageChart extends BaseChart {
this.max_slices = 10;
this.max_legend_points = 6;

this.colors = args.colors;

if(!this.colors || this.colors.length < this.data.labels.length) {
this.colors = ['light-blue', 'blue', 'violet', 'red', 'orange',
'yellow', 'green', 'light-green', 'purple', 'magenta'];
}

this.setup();
}

@@ -86,8 +79,6 @@ export default class PercentageChart extends BaseChart {
this.legend_totals = this.slice_totals.slice(0, this.max_legend_points);
}

setup_utils() { }

make_graph_components() {
this.grand_total = this.slice_totals.reduce((a, b) => a + b, 0);
this.slices = [];


+ 0
- 1
src/scripts/charts/PieChart.js 查看文件

@@ -66,7 +66,6 @@ export default class PieChart extends BaseChart {
this.legend_totals = this.slice_totals.slice(0, this.max_legend_points);
}

setup_utils() { }
static getPositionByAngle(angle,radius){
return {
x:Math.sin(angle * ANGLE_RATIO) * radius,


+ 34
- 0
src/scripts/utils/date-utils.js 查看文件

@@ -0,0 +1,34 @@
// Playing around with dates

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

export function get_dd_mm_yyyy(date) {
let dd = date.getDate();
let mm = date.getMonth() + 1; // getMonth() is zero-based
return [
(dd>9 ? '' : '0') + dd,
(mm>9 ? '' : '0') + mm,
date.getFullYear()
].join('-');
}

export function get_weeks_between(start_date_str, end_date_str) {
return Math.ceil(get_days_between(start_date_str, end_date_str) / 7);
}

export function get_days_between(start_date_str, end_date_str) {
let milliseconds_per_day = 24 * 60 * 60 * 1000;
return (treat_as_utc(end_date_str) - treat_as_utc(start_date_str)) / milliseconds_per_day;
}

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

// export function get_month_name() {}

+ 54
- 8
src/scripts/utils/draw.js 查看文件

@@ -83,18 +83,64 @@ export var UnitRenderer = (function() {


export function make_x_line(height, text_start_at, point, label_class, axis_line_class, x_pos) {

let line = $.createSVG('line', {
x1: 0,
x2: 0,
y1: 0,
y2: height
});

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

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

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

return x_line;
}

export function make_y_line() {}

export function draw_x_line() {}

export function draw_y_line() {}
export function make_y_line(start_at, width, text_end_at, point, label_class, axis_line_class, y_pos, darker=false, line_type="") {
let line = $.createSVG('line', {
className: line_type === "dashed" ? "dashed": "",
x1: start_at,
x2: width,
y1: 0,
y2: 0
});

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

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

if(darker) {
line.style.stroke = "rgba(27, 31, 35, 0.6)";
}

export function label_x_line() {}
y_line.appendChild(line);
y_line.appendChild(text);

export function label_y_line() {}
return y_line;
}

export function get_anim_x_line() {}



+ 113
- 2
src/scripts/utils/intervals.js 查看文件

@@ -28,6 +28,117 @@ export function clump_intervals(start, interval_size, count) {
return intervals;
}

export function calc_intervals() {
//
// export function calc_intervals() {
// //
// }

export function calc_y_intervals(array) {
//*** Where the magic happens ***

// Calculates best-fit y intervals from given values
// and returns the interval array

// TODO: Fractions

let max_bound, min_bound, pos_no_of_parts, neg_no_of_parts, part_size; // eslint-disable-line no-unused-vars

// Critical values
let max_val = parseInt(Math.max(...array));
let min_val = parseInt(Math.min(...array));
if(min_val >= 0) {
min_val = 0;
}

let get_params = (value1, value2) => {
let bound1, bound2, no_of_parts_1, no_of_parts_2, interval_size;
if((value1+"").length <= 1) {
[bound1, no_of_parts_1] = [10, 5];
} else {
[bound1, no_of_parts_1] = calc_upper_bound_and_no_of_parts(value1);
}

interval_size = bound1 / no_of_parts_1;
no_of_parts_2 = calc_no_of_parts(value2, interval_size);
bound2 = no_of_parts_2 * interval_size;

return [bound1, bound2, no_of_parts_1, no_of_parts_2, interval_size];
};

const abs_min_val = min_val * -1;
if(abs_min_val <= max_val) {
// Get the positive region intervals
// then calc negative ones accordingly
[max_bound, min_bound, pos_no_of_parts, neg_no_of_parts, part_size]
= get_params(max_val, abs_min_val);
if(abs_min_val === 0) {
min_bound = 0; neg_no_of_parts = 0;
}
} else {
// Get the negative region here first
[min_bound, max_bound, neg_no_of_parts, pos_no_of_parts, part_size]
= get_params(abs_min_val, max_val);
}

// Make both region parts even
if(pos_no_of_parts % 2 !== 0 && neg_no_of_parts > 0) pos_no_of_parts++;
if(neg_no_of_parts % 2 !== 0) {
// every increase in no_of_parts entails an increase in corresponding bound
// except here, it happens implicitly after every calc_no_of_parts() call
neg_no_of_parts++;
min_bound += part_size;
}

let no_of_parts = pos_no_of_parts + neg_no_of_parts;
if(no_of_parts > 5) {
no_of_parts /= 2;
part_size *= 2;

pos_no_of_parts /=2;
}

if (max_val < (pos_no_of_parts - 1) * part_size) {
no_of_parts--;
}

return get_intervals(
(-1) * min_bound,
part_size,
no_of_parts
);
}

function get_intervals(start, interval_size, count) {
let intervals = [];
for(var i = 0; i <= count; i++){
intervals.push(start);
start += interval_size;
}
return intervals;
}

function calc_upper_bound_and_no_of_parts(max_val) {
// Given a positive value, calculates a nice-number upper bound
// and a consequent optimal number of parts

const part_size = Math.pow(10, ((max_val+"").length - 1));
const no_of_parts = calc_no_of_parts(max_val, part_size);

// Use it to get a nice even upper bound
const upper_bound = part_size * no_of_parts;

return [upper_bound, no_of_parts];
}

function calc_no_of_parts(value, divisor) {
// value should be a positive number, divisor should be greater than 0
// returns an even no of parts
let no_of_parts = Math.ceil(value / divisor);
if(no_of_parts % 2 !== 0) no_of_parts++; // Make it an even number

return no_of_parts;
}

function get_optimal_no_of_parts(no_of_parts) {
// aka Divide by 2 if too large
return (no_of_parts < 5) ? no_of_parts : no_of_parts / 2;
}

正在加载...
取消
保存