config.js, renaming

This commit is contained in:
pratu16x7 2017-12-03 15:39:29 +05:30
parent 863bd45724
commit f2616bfa08
17 changed files with 515 additions and 386 deletions

View File

@ -35,7 +35,7 @@ $.create = (tag, o) => {
return element;
};
function get_offset(element) {
function getOffset(element) {
let rect = element.getBoundingClientRect();
return {
// https://stackoverflow.com/a/7436602/6495043
@ -662,6 +662,33 @@ const COLOR_COMPATIBLE_CHARTS = {
heatmap: []
};
function getDifferentChart(type, current_type, args) {
if(type === current_type) return;
if(!ALL_CHART_TYPES.includes(type)) {
console.error(`'${type}' is not a valid chart type.`);
}
if(!COMPATIBLE_CHARTS[current_type].includes(type)) {
console.error(`'${current_type}' chart cannot be converted to a '${type}' chart.`);
}
// whether the new chart can use the existing colors
const useColor = COLOR_COMPATIBLE_CHARTS[current_type].includes(type);
// Okay, this is anticlimactic
// this function will need to actually be 'changeChartType(type)'
// that will update only the required elements, but for now ...
return new Chart({
parent: args.parent,
title: args.title,
data: args.data,
type: type,
height: args.height,
colors: useColor ? args.colors : undefined
});
}
class BaseChart {
constructor({
height = 240,
@ -670,34 +697,33 @@ class BaseChart {
subtitle = '',
colors = [],
is_navigable = 0,
isNavigable = 0,
type = '',
parent,
data
}) {
this.raw_chart_args = arguments[0];
this.rawChartArgs = arguments[0];
this.parent = typeof parent === 'string' ? document.querySelector(parent) : parent;
this.title = title;
this.subtitle = subtitle;
this.data = data;
this.is_navigable = is_navigable;
if(this.is_navigable) {
this.current_index = 0;
this.isNavigable = isNavigable;
if(this.isNavigable) {
this.currentIndex = 0;
}
this.setupConfiguration(arguments[0]);
this.setupConfiguration();
}
setupConfiguration(args) {
setupConfiguration() {
// Make a this.config, that has stuff like showTooltip,
// showLegend, which then all functions will check
this.setColors(args.colors, args.type);
this.set_margins(args.height);
this.setColors();
this.setMargins();
this.config = {
showTooltip: 1,
@ -706,26 +732,29 @@ class BaseChart {
};
}
setColors(colors, type) {
this.colors = colors;
setColors() {
let args = this.rawChartArgs;
// Needs structure as per only labels/datasets
const list = type === 'percentage' || type === 'pie'
? this.data.labels
: this.data.datasets;
// Needs structure as per only labels/datasets, from config
const list = args.type === 'percentage' || args.type === 'pie'
? args.data.labels
: args.data.datasets;
if(!this.colors || (list && this.colors.length < list.length)) {
if(!args.colors || (list && args.colors.length < list.length)) {
this.colors = DEFAULT_COLORS;
} else {
this.colors = args.colors;
}
this.colors = this.colors.map(color => getColor(color));
}
set_margins(height) {
setMargins() {
let height = this.rawChartArgs.height;
this.baseHeight = height;
this.height = height - 40;
this.translate_x = 60;
this.translate_y = 10;
this.translateX = 60;
this.translateY = 10;
}
validate(){
@ -733,13 +762,22 @@ class BaseChart {
console.error("No parent element to render on was provided.");
return false;
}
if(!this.validateAndPrepareData()) {
if(!this.parseData()) {
return false;
}
return true;
}
validateAndPrepareData() {
parseData() {
let data = this.rawChartArgs.data;
// Check and all
// If all good
this.data = data;
return true;
}
@ -753,8 +791,6 @@ class BaseChart {
this.bindWindowEvents();
this.setupConstants();
// this.setupEmptyValues();
// this.setupComponents();
this.makeContainer();
this.makeTooltip(); // without binding
@ -763,12 +799,11 @@ class BaseChart {
draw(init=false) {
// (draw everything, layers, groups, units)
this.setWidth();
// these both dependent on width >.<, how can this be decoupled
this.setupEmptyValues();
this.calc();
this.setupRenderer(); // this chart's rendered with the config
this.setupComponents();
this.makeChartArea();
this.makeLayers();
@ -779,40 +814,32 @@ class BaseChart {
if(init) this.update(this.data);
}
update(data, animate=true) {
this.oldData = Object.assign({}, this.data);
this.data = this.prepareNewData(data);
this.calculateValues();
this.updateComponents(animate);
}
prepareNewData(newData) {
// handle all types of passed data?
return newData;
}
bindWindowEvents() {
window.addEventListener('resize', () => this.draw());
window.addEventListener('orientationchange', () => this.draw());
}
setWidth() {
let special_values_width = 0;
// let char_width = 8;
// this.specific_values.map(val => {
// let str_width = getStringWidth((val.title + ""), char_width);
// if(str_width > special_values_width) {
// special_values_width = str_width - 40;
calcWidth() {
let outerAnnotationsWidth = 0;
// let charWidth = 8;
// this.specificValues.map(val => {
// let strWidth = getStringWidth((val.title + ""), charWidth);
// if(strWidth > outerAnnotationsWidth) {
// outerAnnotationsWidth = strWidth - 40;
// }
// });
this.baseWidth = getElementContentWidth(this.parent) - special_values_width;
this.width = this.baseWidth - this.translate_x * 2;
this.baseWidth = getElementContentWidth(this.parent) - outerAnnotationsWidth;
this.width = this.baseWidth - this.translateX * 2;
}
setupConstants() {}
setupEmptyValues() {}
calc() {
this.calcWidth();
this.reCalc();
}
setupRenderer() {}
setupComponents() {
// Components config
@ -832,13 +859,13 @@ class BaseChart {
this.parent.innerHTML = '';
this.parent.appendChild(this.container);
this.chart_wrapper = this.container.querySelector('.frappe-chart');
this.stats_wrapper = this.container.querySelector('.graph-stats-container');
this.chartWrapper = this.container.querySelector('.frappe-chart');
this.statsWrapper = this.container.querySelector('.graph-stats-container');
}
makeChartArea() {
this.svg = makeSVGContainer(
this.chart_wrapper,
this.chartWrapper,
'chart',
this.baseWidth,
this.baseHeight
@ -848,7 +875,7 @@ class BaseChart {
this.drawArea = makeSVGGroup(
this.svg,
this.type + '-chart',
`translate(${this.translate_x}, ${this.translate_y})`
`translate(${this.translateX}, ${this.translateY})`
);
}
@ -869,72 +896,88 @@ class BaseChart {
});
}
updateComponents() {
// this.components.forEach((component) => {
// //
// });
update() {
this.reCalc();
this.reRender();
}
reCalc() {
// Will update values(state)
// Will recalc specific parts depending on the update
}
reRender(animate=true) {
if(!animate) {
this.renderComponents();
return;
}
this.animateComponents();
setTimeout(() => {
this.renderComponents();
}, 400);
// TODO: should be max anim duration required
// (opt, should not redraw if still in animate?)
}
animateComponents() {
this.intermedValues = this.calcIntermediateValues();
this.components.forEach(c => {
// c.store = c.animate(...c.animateArgs);
// c.layer.textContent = '';
// c.store.forEach(element => {c.layer.appendChild(element);});
});
}
calcInitStage() {}
makeTooltip() {
this.tip = new SvgTip({
parent: this.chart_wrapper,
parent: this.chartWrapper,
colors: this.colors
});
this.bind_tooltip();
this.bindTooltip();
}
show_summary() {}
show_custom_summary() {
this.summary.map(d => {
let stats = $.create('div', {
className: 'stats',
innerHTML: `<span class="indicator">
<i style="background:${d.color}"></i>
${d.title}: ${d.value}
</span>`
});
this.stats_wrapper.appendChild(stats);
});
}
renderLegend() {}
setupNavigation(init=false) {
if(this.is_navigable) return;
if(this.isNavigable) return;
this.make_overlay();
this.makeOverlay();
if(init) {
this.bind_overlay();
this.bindOverlay();
document.addEventListener('keydown', (e) => {
if(isElementInViewport(this.chart_wrapper)) {
if(isElementInViewport(this.chartWrapper)) {
e = e || window.event;
if (e.keyCode == '37') {
this.on_left_arrow();
this.onLeftArrow();
} else if (e.keyCode == '39') {
this.on_right_arrow();
this.onRightArrow();
} else if (e.keyCode == '38') {
this.on_up_arrow();
this.onUpArrow();
} else if (e.keyCode == '40') {
this.on_down_arrow();
this.onDownArrow();
} else if (e.keyCode == '13') {
this.on_enter_key();
this.onEnterKey();
}
}
});
}
}
make_overlay() {}
bind_overlay() {}
makeOverlay() {}
bindOverlay() {}
bind_units() {}
on_left_arrow() {}
on_right_arrow() {}
on_up_arrow() {}
on_down_arrow() {}
on_enter_key() {}
onLeftArrow() {}
onRightArrow() {}
onUpArrow() {}
onDownArrow() {}
onEnterKey() {}
getDataPoint() {}
updateCurrentDataPoint() {}
@ -943,31 +986,8 @@ class BaseChart {
return makeSVGGroup(this.drawArea, className, transform);
}
get_different_chart(type) {
if(type === this.type) return;
if(!ALL_CHART_TYPES.includes(type)) {
console.error(`'${type}' is not a valid chart 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_CHARTS[this.type].includes(type);
// Okay, this is anticlimactic
// this function will need to actually be 'change_chart_type(type)'
// that will update only the required elements, but for now ...
return new Chart({
parent: this.raw_chart_args.parent,
title: this.title,
data: this.raw_chart_args.data,
type: type,
height: this.raw_chart_args.height,
colors: use_color ? this.colors : undefined
});
getDifferentChart(type) {
return getDifferentChart(type, this.type, this.rawChartArgs);
}
}
@ -1374,9 +1394,10 @@ class AxisChart extends BaseChart {
this.zero_line = this.height;
}
validateAndPrepareData() {
this.xAxisLabels = this.data.labels || [];
this.y = this.data.datasets || [];
parseData() {
let args = this.rawChartArgs;
this.xAxisLabels = args.data.labels || [];
this.y = args.data.datasets || [];
this.y.forEach(function(d, i) {
d.index = i;
@ -1384,25 +1405,67 @@ class AxisChart extends BaseChart {
return true;
}
setupEmptyValues() {
reCalc() {
// examples:
// [A] Dimension change:
// [B] Data change:
// 1. X values update
// 2. Y values update
// Aka all the values(state), these all will be documented in an old values object
// Backup first!
this.oldValues = ["everything"];
// extracted, raw args will remain in their own object
this.datasetsLabels = [];
this.datasetsValues = [[[12, 34, 68], [10, 5, 46]], [[20, 20, 20]]];
// CALCULATION: we'll need the first batch of calcs
// List of what will happen:
// this.xOffset = 0;
// this.unitWidth = 0;
// this.scaleMultipliers = [];
// this.datasetsPoints =
// Now, the function calls
// var merged = [].concat(...arrays)
// INIT
// axes
this.yAxisPositions = [this.height, this.height/2, 0];
this.yAxisLabels = ['0', '5', '10'];
this.xPositions = [0, this.width/2, this.width];
this.xAxisLabels = ['0', '5', '10'];
}
calcInitStage() {
// will borrow from the full recalc function
}
calcIntermediateValues() {
//
}
// this should be inherent in BaseChart
getRenderer() {
// These args are basically the current state/config of the chart,
// with constant and alive params mixed
return new AxisChartRenderer(this.height, this.width,
this.zero_line, this.avg_unit_width, this.xAxisMode, this.yAxisMode);
}
setupComponents() {
// Must have access to all current data things
let self = this;
let renderer = this.getRenderer();
this.yAxis = {
@ -1411,6 +1474,7 @@ class AxisChart extends BaseChart {
make: self.makeYLines,
makeArgs: [renderer, self.yAxisPositions, self.yAxisLabels],
store: [],
// animate? or update? will come to while implementing
animate: self.animateYLines,
// indexed: 1 // ?? As per datasets?
};
@ -1424,6 +1488,16 @@ class AxisChart extends BaseChart {
store: [],
animate: self.animateXLines
};
// Indexed according to dataset
// this.dataUnits = {
// layerClass: 'y marker axis',
// layer: undefined,
// make: makeXLines,
// makeArgs: [this.xPositions, this.xAxisLabels],
// store: [],
// animate: animateXLines,
// indexed: 1
// };
this.yMarkerLines = {
// layerClass: 'y marker axis',
// layer: undefined,
@ -1436,24 +1510,13 @@ class AxisChart extends BaseChart {
// layerClass: 'x marker axis',
// layer: undefined,
// make: makeXMarkerLines,
// makeArgs: [this.yMarkerPositions, this.xMarker],
// makeArgs: [this.xMarkerPositions, this.xMarker],
// store: [],
// animate: animateXMarkerLines
};
// Marker Regions
// Indexed according to dataset
this.dataUnits = {
layerClass: 'y marker axis',
layer: undefined,
// make: makeXLines,
// makeArgs: [this.xPositions, this.xAxisLabels],
// store: [],
// animate: animateXLines,
indexed: 1
};
this.components = [
this.yAxis,
this.xAxis,
@ -1639,19 +1702,19 @@ class AxisChart extends BaseChart {
units_array.push(data_unit);
});
if(this.is_navigable) {
if(this.isNavigable) {
this.bind_units(units_array);
}
}
bind_tooltip() {
bindTooltip() {
// TODO: could be in tooltip itself, as it is a given functionality for its parent
this.chart_wrapper.addEventListener('mousemove', (e) => {
let offset = get_offset(this.chart_wrapper);
let relX = e.pageX - offset.left - this.translate_x;
let relY = e.pageY - offset.top - this.translate_y;
this.chartWrapper.addEventListener('mousemove', (e) => {
let offset = getOffset(this.chartWrapper);
let relX = e.pageX - offset.left - this.translateX;
let relY = e.pageY - offset.top - this.translateY;
if(relY < this.height + this.translate_y * 2) {
if(relY < this.height + this.translateY * 2) {
this.mapTooltipXPosition(relX);
} else {
this.tip.hide_tip();
@ -1673,8 +1736,8 @@ class AxisChart extends BaseChart {
let x_val = this.xPositions[i];
// let delta = i === 0 ? this.avg_unit_width : x_val - this.xPositions[i-1];
if(relX > x_val - this.avg_unit_width/2) {
let x = x_val + this.translate_x;
let y = this.y_min_tops[i] + this.translate_y;
let x = x_val + this.translateX;
let y = this.y_min_tops[i] + this.translateY;
let title = titles[i];
let values = this.y.map((set, j) => {
@ -1766,7 +1829,7 @@ class AxisChart extends BaseChart {
this.animate_units(d, newX, newY);
});
runSMILAnimation(this.chart_wrapper, this.svg, this.elements_to_animate);
runSMILAnimation(this.chartWrapper, this.svg, this.elements_to_animate);
setTimeout(() => {
this.y.map(d => {
@ -1841,7 +1904,7 @@ class AxisChart extends BaseChart {
this.updateData(newY, newX);
}
getDataPoint(index=this.current_index) {
getDataPoint(index=this.currentIndex) {
// check for length
let data_point = {
index: index
@ -1859,8 +1922,8 @@ class AxisChart extends BaseChart {
index = parseInt(index);
if(index < 0) index = 0;
if(index >= this.xAxisLabels.length) index = this.xAxisLabels.length - 1;
if(index === this.current_index) return;
this.current_index = index;
if(index === this.currentIndex) return;
this.currentIndex = index;
fire(this.parent, "data-select", this.getDataPoint());
}
@ -1892,7 +1955,7 @@ class AxisChart extends BaseChart {
}
});
});
// this.chart_wrapper.removeChild(this.tip.container);
// this.chartWrapper.removeChild(this.tip.container);
// this.make_tooltip();
}
}
@ -1918,7 +1981,7 @@ class BarChart extends AxisChart {
};
}
// make_overlay() {
// makeOverlay() {
// // Just make one out of the first element
// let index = this.xAxisLabels.length - 1;
// let unit = this.y[0].svg_units[index];
@ -1933,7 +1996,7 @@ class BarChart extends AxisChart {
// this.drawArea.appendChild(this.overlay);
// }
// bind_overlay() {
// bindOverlay() {
// // on event, update overlay
// this.parent.addEventListener('data-select', (e) => {
// this.update_overlay(e.svg_unit);
@ -1963,12 +2026,12 @@ class BarChart extends AxisChart {
this.overlay.style.opacity = '0.4';
}
on_left_arrow() {
this.updateCurrentDataPoint(this.current_index - 1);
onLeftArrow() {
this.updateCurrentDataPoint(this.currentIndex - 1);
}
on_right_arrow() {
this.updateCurrentDataPoint(this.current_index + 1);
onRightArrow() {
this.updateCurrentDataPoint(this.currentIndex + 1);
}
set_avg_unit_width_and_x_offset() {
@ -2101,16 +2164,16 @@ class PercentageChart extends BaseChart {
}
makeChartArea() {
this.chart_wrapper.className += ' ' + 'graph-focus-margin';
this.chart_wrapper.style.marginTop = '45px';
this.chartWrapper.className += ' ' + 'graph-focus-margin';
this.chartWrapper.style.marginTop = '45px';
this.stats_wrapper.className += ' ' + 'graph-focus-margin';
this.stats_wrapper.style.marginBottom = '30px';
this.stats_wrapper.style.paddingTop = '0px';
this.statsWrapper.className += ' ' + 'graph-focus-margin';
this.statsWrapper.style.marginBottom = '30px';
this.statsWrapper.style.paddingTop = '0px';
this.chartDiv = $.create('div', {
className: 'div',
inside: this.chart_wrapper
inside: this.chartWrapper
});
this.chart = $.create('div', {
@ -2177,10 +2240,10 @@ class PercentageChart extends BaseChart {
});
}
bind_tooltip() {
bindTooltip() {
this.slices.map((slice, i) => {
slice.addEventListener('mouseenter', () => {
let g_off = get_offset(this.chart_wrapper), p_off = get_offset(slice);
let g_off = getOffset(this.chartWrapper), p_off = getOffset(slice);
let x = p_off.left - g_off.left + slice.offsetWidth/2;
let y = p_off.top - g_off.top - 6;
@ -2201,7 +2264,7 @@ class PercentageChart extends BaseChart {
if(d) {
let stats = $.create('div', {
className: 'stats',
inside: this.stats_wrapper
inside: this.statsWrapper
});
stats.innerHTML = `<span class="indicator">
<i style="background: ${this.colors[i]}"></i>
@ -2328,7 +2391,7 @@ class PieChart extends BaseChart {
});
if(init){
runSMILAnimation(this.chart_wrapper, this.svg, this.elements_to_animate);
runSMILAnimation(this.chartWrapper, this.svg, this.elements_to_animate);
}
}
@ -2343,7 +2406,7 @@ class PieChart extends BaseChart {
if(flag){
transform(path,this.calTranslateByAngle(this.slicesProperties[i]));
path.style.fill = lightenDarkenColor(color,50);
let g_off = get_offset(this.svg);
let g_off = getOffset(this.svg);
let x = e.pageX - g_off.left + 10;
let y = e.pageY - g_off.top - 10;
let title = (this.formatted_labels && this.formatted_labels.length>0
@ -2375,7 +2438,7 @@ class PieChart extends BaseChart {
mouseLeave(){
this.hoverSlice(this.curActiveSlice,this.curActiveSliceIndex,false);
}
bind_tooltip() {
bindTooltip() {
this.drawArea.addEventListener('mousemove',this.mouseMove);
this.drawArea.addEventListener('mouseleave',this.mouseLeave);
}
@ -2389,7 +2452,7 @@ class PieChart extends BaseChart {
if(d) {
let stats = $.create('div', {
className: 'stats',
inside: this.stats_wrapper
inside: this.statsWrapper
});
stats.innerHTML = `<span class="indicator">
<i style="background-color:${color};"></i>
@ -2468,7 +2531,7 @@ class Heatmap extends BaseChart {
// More colors are difficult to parse visually
this.distribution_size = 5;
this.translate_x = 0;
this.translateX = 0;
// this.setup();
}
@ -2504,7 +2567,7 @@ class Heatmap extends BaseChart {
this.no_of_cols = getWeeksBetween(this.first_week_start + '', this.last_week_start + '') + 1;
}
setWidth() {
calcWidth() {
this.baseWidth = (this.no_of_cols + 3) * 12 ;
if(this.discrete_domains) {
@ -2659,11 +2722,11 @@ class Heatmap extends BaseChart {
).map(d => {
d.style.display = 'None';
});
this.chart_wrapper.style.marginTop = '0px';
this.chart_wrapper.style.paddingTop = '0px';
this.chartWrapper.style.marginTop = '0px';
this.chartWrapper.style.paddingTop = '0px';
}
bind_tooltip() {
bindTooltip() {
Array.prototype.slice.call(
document.querySelectorAll(".data-group .day")
).map(el => {
@ -2673,7 +2736,7 @@ class Heatmap extends BaseChart {
let month = this.month_names[parseInt(date_parts[1])-1].substring(0, 3);
let g_off = this.chart_wrapper.getBoundingClientRect(), p_off = e.target.getBoundingClientRect();
let g_off = this.chartWrapper.getBoundingClientRect(), p_off = e.target.getBoundingClientRect();
let width = parseInt(e.target.getAttribute('width'));
let x = p_off.left - g_off.left + (width+2)/2;
@ -2690,7 +2753,7 @@ class Heatmap extends BaseChart {
update(data) {
this.data = data;
this.setup_values();
this.bind_tooltip();
this.bindTooltip();
}
}

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

@ -50,7 +50,7 @@ let bar_composite_chart = new Chart ({
type: 'bar',
height: 180,
colors: ['orange'],
is_navigable: 1,
isNavigable: 1,
is_series: 1
// region_fill: 1
});
@ -110,7 +110,7 @@ Array.prototype.slice.call(
let btn = e.target;
let type = btn.getAttribute('data-type');
let newChart = type_chart.get_different_chart(type);
let newChart = type_chart.getDifferentChart(type);
if(newChart){
type_chart = newChart;
}
@ -300,7 +300,7 @@ let events_chart = new Chart({
type: 'bar',
height: 250,
colors: ['grey'],
is_navigable: 1,
isNavigable: 1,
});
let data_div = document.querySelector('.chart-events-data');

View File

@ -205,7 +205,7 @@
</div>
<pre><code class="hljs javascript margin-vertical-px"> ...
type: 'bar', // Bar Chart specific properties:
is_navigable: 1, // Navigate across bars; default 0
isNavigable: 1, // Navigate across bars; default 0
...
chart.parent.addEventListener('data-select', (e) => {

View File

@ -15,7 +15,7 @@ import pkg from './package.json';
export default [
{
input: 'src/js/charts.js',
input: 'src/js/chart.js',
output: [
{
file: 'docs/assets/js/frappe-charts.min.js',
@ -56,7 +56,7 @@ export default [
]
},
{
input: 'src/js/charts.js',
input: 'src/js/chart.js',
output: [
{
file: pkg.main,
@ -96,7 +96,7 @@ export default [
],
},
{
input: 'src/js/charts.js',
input: 'src/js/chart.js',
output: [
{
file: pkg.src,

View File

@ -1,5 +1,5 @@
import BaseChart from './BaseChart';
import { get_offset, fire } from '../utils/dom';
import { getOffset, fire } from '../utils/dom';
import { AxisChartRenderer } from '../utils/draw';
import { equilizeNoOfElements } from '../utils/draw-utils';
import { Animator } from '../utils/animate';
@ -16,9 +16,10 @@ export default class AxisChart extends BaseChart {
this.zero_line = this.height;
}
validateAndPrepareData() {
this.xAxisLabels = this.data.labels || [];
this.y = this.data.datasets || [];
parseData() {
let args = this.rawChartArgs;
this.xAxisLabels = args.data.labels || [];
this.y = args.data.datasets || [];
this.y.forEach(function(d, i) {
d.index = i;
@ -26,25 +27,67 @@ export default class AxisChart extends BaseChart {
return true;
}
setupEmptyValues() {
reCalc() {
// examples:
// [A] Dimension change:
// [B] Data change:
// 1. X values update
// 2. Y values update
// Aka all the values(state), these all will be documented in an old values object
// Backup first!
this.oldValues = ["everything"];
// extracted, raw args will remain in their own object
this.datasetsLabels = [];
this.datasetsValues = [[[12, 34, 68], [10, 5, 46]], [[20, 20, 20]]];
// CALCULATION: we'll need the first batch of calcs
// List of what will happen:
// this.xOffset = 0;
// this.unitWidth = 0;
// this.scaleMultipliers = [];
// this.datasetsPoints =
// Now, the function calls
// var merged = [].concat(...arrays)
// INIT
// axes
this.yAxisPositions = [this.height, this.height/2, 0];
this.yAxisLabels = ['0', '5', '10'];
this.xPositions = [0, this.width/2, this.width];
this.xAxisLabels = ['0', '5', '10'];
}
calcInitStage() {
// will borrow from the full recalc function
}
calcIntermediateValues() {
//
}
// this should be inherent in BaseChart
getRenderer() {
// These args are basically the current state/config of the chart,
// with constant and alive params mixed
return new AxisChartRenderer(this.height, this.width,
this.zero_line, this.avg_unit_width, this.xAxisMode, this.yAxisMode);
}
setupComponents() {
// Must have access to all current data things
let self = this;
let renderer = this.getRenderer();
this.yAxis = {
@ -53,6 +96,7 @@ export default class AxisChart extends BaseChart {
make: self.makeYLines,
makeArgs: [renderer, self.yAxisPositions, self.yAxisLabels],
store: [],
// animate? or update? will come to while implementing
animate: self.animateYLines,
// indexed: 1 // ?? As per datasets?
};
@ -66,6 +110,16 @@ export default class AxisChart extends BaseChart {
store: [],
animate: self.animateXLines
};
// Indexed according to dataset
// this.dataUnits = {
// layerClass: 'y marker axis',
// layer: undefined,
// make: makeXLines,
// makeArgs: [this.xPositions, this.xAxisLabels],
// store: [],
// animate: animateXLines,
// indexed: 1
// };
this.yMarkerLines = {
// layerClass: 'y marker axis',
// layer: undefined,
@ -78,24 +132,13 @@ export default class AxisChart extends BaseChart {
// layerClass: 'x marker axis',
// layer: undefined,
// make: makeXMarkerLines,
// makeArgs: [this.yMarkerPositions, this.xMarker],
// makeArgs: [this.xMarkerPositions, this.xMarker],
// store: [],
// animate: animateXMarkerLines
};
// Marker Regions
// Indexed according to dataset
this.dataUnits = {
layerClass: 'y marker axis',
layer: undefined,
// make: makeXLines,
// makeArgs: [this.xPositions, this.xAxisLabels],
// store: [],
// animate: animateXLines,
indexed: 1
};
this.components = [
this.yAxis,
this.xAxis,
@ -281,19 +324,19 @@ export default class AxisChart extends BaseChart {
units_array.push(data_unit);
});
if(this.is_navigable) {
if(this.isNavigable) {
this.bind_units(units_array);
}
}
bind_tooltip() {
bindTooltip() {
// TODO: could be in tooltip itself, as it is a given functionality for its parent
this.chart_wrapper.addEventListener('mousemove', (e) => {
let offset = get_offset(this.chart_wrapper);
let relX = e.pageX - offset.left - this.translate_x;
let relY = e.pageY - offset.top - this.translate_y;
this.chartWrapper.addEventListener('mousemove', (e) => {
let offset = getOffset(this.chartWrapper);
let relX = e.pageX - offset.left - this.translateX;
let relY = e.pageY - offset.top - this.translateY;
if(relY < this.height + this.translate_y * 2) {
if(relY < this.height + this.translateY * 2) {
this.mapTooltipXPosition(relX);
} else {
this.tip.hide_tip();
@ -315,8 +358,8 @@ export default class AxisChart extends BaseChart {
let x_val = this.xPositions[i];
// let delta = i === 0 ? this.avg_unit_width : x_val - this.xPositions[i-1];
if(relX > x_val - this.avg_unit_width/2) {
let x = x_val + this.translate_x;
let y = this.y_min_tops[i] + this.translate_y;
let x = x_val + this.translateX;
let y = this.y_min_tops[i] + this.translateY;
let title = titles[i];
let values = this.y.map((set, j) => {
@ -408,7 +451,7 @@ export default class AxisChart extends BaseChart {
this.animate_units(d, newX, newY);
});
runSMILAnimation(this.chart_wrapper, this.svg, this.elements_to_animate);
runSMILAnimation(this.chartWrapper, this.svg, this.elements_to_animate);
setTimeout(() => {
this.y.map(d => {
@ -483,7 +526,7 @@ export default class AxisChart extends BaseChart {
this.updateData(newY, newX);
}
getDataPoint(index=this.current_index) {
getDataPoint(index=this.currentIndex) {
// check for length
let data_point = {
index: index
@ -501,8 +544,8 @@ export default class AxisChart extends BaseChart {
index = parseInt(index);
if(index < 0) index = 0;
if(index >= this.xAxisLabels.length) index = this.xAxisLabels.length - 1;
if(index === this.current_index) return;
this.current_index = index;
if(index === this.currentIndex) return;
this.currentIndex = index;
fire(this.parent, "data-select", this.getDataPoint());
}
@ -534,7 +577,7 @@ export default class AxisChart extends BaseChart {
}
});
});
// this.chart_wrapper.removeChild(this.tip.container);
// this.chartWrapper.removeChild(this.tip.container);
// this.make_tooltip();
}
}

View File

@ -21,7 +21,7 @@ export default class BarChart extends AxisChart {
};
}
// make_overlay() {
// makeOverlay() {
// // Just make one out of the first element
// let index = this.xAxisLabels.length - 1;
// let unit = this.y[0].svg_units[index];
@ -36,7 +36,7 @@ export default class BarChart extends AxisChart {
// this.drawArea.appendChild(this.overlay);
// }
// bind_overlay() {
// bindOverlay() {
// // on event, update overlay
// this.parent.addEventListener('data-select', (e) => {
// this.update_overlay(e.svg_unit);
@ -66,12 +66,12 @@ export default class BarChart extends AxisChart {
this.overlay.style.opacity = '0.4';
}
on_left_arrow() {
this.updateCurrentDataPoint(this.current_index - 1);
onLeftArrow() {
this.updateCurrentDataPoint(this.currentIndex - 1);
}
on_right_arrow() {
this.updateCurrentDataPoint(this.current_index + 1);
onRightArrow() {
this.updateCurrentDataPoint(this.currentIndex + 1);
}
set_avg_unit_width_and_x_offset() {

View File

@ -3,28 +3,7 @@ import { $, isElementInViewport, getElementContentWidth } from '../utils/dom';
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: []
};
// 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: []
};
import { getDifferentChart } from '../config';
export default class BaseChart {
constructor({
@ -34,34 +13,33 @@ export default class BaseChart {
subtitle = '',
colors = [],
is_navigable = 0,
isNavigable = 0,
type = '',
parent,
data
}) {
this.raw_chart_args = arguments[0];
this.rawChartArgs = arguments[0];
this.parent = typeof parent === 'string' ? document.querySelector(parent) : parent;
this.title = title;
this.subtitle = subtitle;
this.data = data;
this.is_navigable = is_navigable;
if(this.is_navigable) {
this.current_index = 0;
this.isNavigable = isNavigable;
if(this.isNavigable) {
this.currentIndex = 0;
}
this.setupConfiguration(arguments[0]);
this.setupConfiguration();
}
setupConfiguration(args) {
setupConfiguration() {
// Make a this.config, that has stuff like showTooltip,
// showLegend, which then all functions will check
this.setColors(args.colors, args.type);
this.set_margins(args.height);
this.setColors();
this.setMargins();
this.config = {
showTooltip: 1,
@ -70,40 +48,54 @@ export default class BaseChart {
};
}
setColors(colors, type) {
this.colors = colors;
setColors() {
let args = this.rawChartArgs;
// Needs structure as per only labels/datasets
const list = type === 'percentage' || type === 'pie'
? this.data.labels
: this.data.datasets;
// Needs structure as per only labels/datasets, from config
const list = args.type === 'percentage' || args.type === 'pie'
? args.data.labels
: args.data.datasets;
if(!this.colors || (list && this.colors.length < list.length)) {
if(!args.colors || (list && args.colors.length < list.length)) {
this.colors = DEFAULT_COLORS;
} else {
this.colors = args.colors;
}
this.colors = this.colors.map(color => getColor(color));
}
set_margins(height) {
setMargins() {
let height = this.rawChartArgs.height;
this.baseHeight = height;
this.height = height - 40;
this.translate_x = 60;
this.translate_y = 10;
this.translateX = 60;
this.translateY = 10;
}
validate(){
let args = this.rawChartArgs;
// Now yo have the args, set this stuff only after validating
if(!this.parent) {
console.error("No parent element to render on was provided.");
return false;
}
if(!this.validateAndPrepareData()) {
if(!this.parseData()) {
return false;
}
return true;
}
validateAndPrepareData() {
parseData() {
let data = this.rawChartArgs.data;
// Check and all
// If all good
this.data = data;
return true;
}
@ -117,8 +109,6 @@ export default class BaseChart {
this.bindWindowEvents();
this.setupConstants();
// this.setupEmptyValues();
// this.setupComponents();
this.makeContainer();
this.makeTooltip(); // without binding
@ -127,12 +117,11 @@ export default class BaseChart {
draw(init=false) {
// (draw everything, layers, groups, units)
this.setWidth();
// these both dependent on width >.<, how can this be decoupled
this.setupEmptyValues();
this.calc();
this.setupRenderer(); // this chart's rendered with the config
this.setupComponents();
this.makeChartArea();
this.makeLayers();
@ -143,40 +132,32 @@ export default class BaseChart {
if(init) this.update(this.data);
}
update(data, animate=true) {
this.oldData = Object.assign({}, this.data);
this.data = this.prepareNewData(data);
this.calculateValues();
this.updateComponents(animate);
}
prepareNewData(newData) {
// handle all types of passed data?
return newData;
}
bindWindowEvents() {
window.addEventListener('resize', () => this.draw());
window.addEventListener('orientationchange', () => this.draw());
}
setWidth() {
let special_values_width = 0;
// let char_width = 8;
// this.specific_values.map(val => {
// let str_width = getStringWidth((val.title + ""), char_width);
// if(str_width > special_values_width) {
// special_values_width = str_width - 40;
calcWidth() {
let outerAnnotationsWidth = 0;
// let charWidth = 8;
// this.specificValues.map(val => {
// let strWidth = getStringWidth((val.title + ""), charWidth);
// if(strWidth > outerAnnotationsWidth) {
// outerAnnotationsWidth = strWidth - 40;
// }
// });
this.baseWidth = getElementContentWidth(this.parent) - special_values_width;
this.width = this.baseWidth - this.translate_x * 2;
this.baseWidth = getElementContentWidth(this.parent) - outerAnnotationsWidth;
this.width = this.baseWidth - this.translateX * 2;
}
setupConstants() {}
setupEmptyValues() {}
calc() {
this.calcWidth();
this.reCalc();
}
setupRenderer() {}
setupComponents() {
// Components config
@ -196,13 +177,13 @@ export default class BaseChart {
this.parent.innerHTML = '';
this.parent.appendChild(this.container);
this.chart_wrapper = this.container.querySelector('.frappe-chart');
this.stats_wrapper = this.container.querySelector('.graph-stats-container');
this.chartWrapper = this.container.querySelector('.frappe-chart');
this.statsWrapper = this.container.querySelector('.graph-stats-container');
}
makeChartArea() {
this.svg = makeSVGContainer(
this.chart_wrapper,
this.chartWrapper,
'chart',
this.baseWidth,
this.baseHeight
@ -212,7 +193,7 @@ export default class BaseChart {
this.drawArea = makeSVGGroup(
this.svg,
this.type + '-chart',
`translate(${this.translate_x}, ${this.translate_y})`
`translate(${this.translateX}, ${this.translateY})`
);
}
@ -233,72 +214,88 @@ export default class BaseChart {
});
}
updateComponents() {
// this.components.forEach((component) => {
// //
// });
update() {
this.reCalc();
this.reRender();
}
reCalc() {
// Will update values(state)
// Will recalc specific parts depending on the update
}
reRender(animate=true) {
if(!animate) {
this.renderComponents();
return;
}
this.animateComponents();
setTimeout(() => {
this.renderComponents();
}, 400);
// TODO: should be max anim duration required
// (opt, should not redraw if still in animate?)
}
animateComponents() {
this.intermedValues = this.calcIntermediateValues();
this.components.forEach(c => {
// c.store = c.animate(...c.animateArgs);
// c.layer.textContent = '';
// c.store.forEach(element => {c.layer.appendChild(element);});
});
}
calcInitStage() {}
makeTooltip() {
this.tip = new SvgTip({
parent: this.chart_wrapper,
parent: this.chartWrapper,
colors: this.colors
});
this.bind_tooltip();
this.bindTooltip();
}
show_summary() {}
show_custom_summary() {
this.summary.map(d => {
let stats = $.create('div', {
className: 'stats',
innerHTML: `<span class="indicator">
<i style="background:${d.color}"></i>
${d.title}: ${d.value}
</span>`
});
this.stats_wrapper.appendChild(stats);
});
}
renderLegend() {}
setupNavigation(init=false) {
if(this.is_navigable) return;
if(this.isNavigable) return;
this.make_overlay();
this.makeOverlay();
if(init) {
this.bind_overlay();
this.bindOverlay();
document.addEventListener('keydown', (e) => {
if(isElementInViewport(this.chart_wrapper)) {
if(isElementInViewport(this.chartWrapper)) {
e = e || window.event;
if (e.keyCode == '37') {
this.on_left_arrow();
this.onLeftArrow();
} else if (e.keyCode == '39') {
this.on_right_arrow();
this.onRightArrow();
} else if (e.keyCode == '38') {
this.on_up_arrow();
this.onUpArrow();
} else if (e.keyCode == '40') {
this.on_down_arrow();
this.onDownArrow();
} else if (e.keyCode == '13') {
this.on_enter_key();
this.onEnterKey();
}
}
});
}
}
make_overlay() {}
bind_overlay() {}
makeOverlay() {}
bindOverlay() {}
bind_units() {}
on_left_arrow() {}
on_right_arrow() {}
on_up_arrow() {}
on_down_arrow() {}
on_enter_key() {}
onLeftArrow() {}
onRightArrow() {}
onUpArrow() {}
onDownArrow() {}
onEnterKey() {}
getDataPoint() {}
updateCurrentDataPoint() {}
@ -307,30 +304,7 @@ export default class BaseChart {
return makeSVGGroup(this.drawArea, className, transform);
}
get_different_chart(type) {
if(type === this.type) return;
if(!ALL_CHART_TYPES.includes(type)) {
console.error(`'${type}' is not a valid chart 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_CHARTS[this.type].includes(type);
// Okay, this is anticlimactic
// this function will need to actually be 'change_chart_type(type)'
// that will update only the required elements, but for now ...
return new Chart({
parent: this.raw_chart_args.parent,
title: this.title,
data: this.raw_chart_args.data,
type: type,
height: this.raw_chart_args.height,
colors: use_color ? this.colors : undefined
});
getDifferentChart(type) {
return getDifferentChart(type, this.type, this.rawChartArgs);
}
}

View File

@ -36,7 +36,7 @@ export default class Heatmap extends BaseChart {
// More colors are difficult to parse visually
this.distribution_size = 5;
this.translate_x = 0;
this.translateX = 0;
// this.setup();
}
@ -72,7 +72,7 @@ export default class Heatmap extends BaseChart {
this.no_of_cols = getWeeksBetween(this.first_week_start + '', this.last_week_start + '') + 1;
}
setWidth() {
calcWidth() {
this.baseWidth = (this.no_of_cols + 3) * 12 ;
if(this.discrete_domains) {
@ -227,11 +227,11 @@ export default class Heatmap extends BaseChart {
).map(d => {
d.style.display = 'None';
});
this.chart_wrapper.style.marginTop = '0px';
this.chart_wrapper.style.paddingTop = '0px';
this.chartWrapper.style.marginTop = '0px';
this.chartWrapper.style.paddingTop = '0px';
}
bind_tooltip() {
bindTooltip() {
Array.prototype.slice.call(
document.querySelectorAll(".data-group .day")
).map(el => {
@ -241,7 +241,7 @@ export default class Heatmap extends BaseChart {
let month = this.month_names[parseInt(date_parts[1])-1].substring(0, 3);
let g_off = this.chart_wrapper.getBoundingClientRect(), p_off = e.target.getBoundingClientRect();
let g_off = this.chartWrapper.getBoundingClientRect(), p_off = e.target.getBoundingClientRect();
let width = parseInt(e.target.getAttribute('width'));
let x = p_off.left - g_off.left + (width+2)/2;
@ -258,6 +258,6 @@ export default class Heatmap extends BaseChart {
update(data) {
this.data = data;
this.setup_values();
this.bind_tooltip();
this.bindTooltip();
}
}

View File

@ -1,5 +1,5 @@
import BaseChart from './BaseChart';
import { $, get_offset } from '../utils/dom';
import { $, getOffset } from '../utils/dom';
export default class PercentageChart extends BaseChart {
constructor(args) {
@ -13,16 +13,16 @@ export default class PercentageChart extends BaseChart {
}
makeChartArea() {
this.chart_wrapper.className += ' ' + 'graph-focus-margin';
this.chart_wrapper.style.marginTop = '45px';
this.chartWrapper.className += ' ' + 'graph-focus-margin';
this.chartWrapper.style.marginTop = '45px';
this.stats_wrapper.className += ' ' + 'graph-focus-margin';
this.stats_wrapper.style.marginBottom = '30px';
this.stats_wrapper.style.paddingTop = '0px';
this.statsWrapper.className += ' ' + 'graph-focus-margin';
this.statsWrapper.style.marginBottom = '30px';
this.statsWrapper.style.paddingTop = '0px';
this.chartDiv = $.create('div', {
className: 'div',
inside: this.chart_wrapper
inside: this.chartWrapper
});
this.chart = $.create('div', {
@ -89,10 +89,10 @@ export default class PercentageChart extends BaseChart {
});
}
bind_tooltip() {
bindTooltip() {
this.slices.map((slice, i) => {
slice.addEventListener('mouseenter', () => {
let g_off = get_offset(this.chart_wrapper), p_off = get_offset(slice);
let g_off = getOffset(this.chartWrapper), p_off = getOffset(slice);
let x = p_off.left - g_off.left + slice.offsetWidth/2;
let y = p_off.top - g_off.top - 6;
@ -113,7 +113,7 @@ export default class PercentageChart extends BaseChart {
if(d) {
let stats = $.create('div', {
className: 'stats',
inside: this.stats_wrapper
inside: this.statsWrapper
});
stats.innerHTML = `<span class="indicator">
<i style="background: ${this.colors[i]}"></i>

View File

@ -1,5 +1,5 @@
import BaseChart from './BaseChart';
import { $, get_offset } from '../utils/dom';
import { $, getOffset } from '../utils/dom';
import { makePath } from '../utils/draw';
import { lightenDarkenColor } from '../utils/colors';
import { runSMILAnimation, transform } from '../utils/animation';
@ -118,7 +118,7 @@ export default class PieChart extends BaseChart {
});
if(init){
runSMILAnimation(this.chart_wrapper, this.svg, this.elements_to_animate);
runSMILAnimation(this.chartWrapper, this.svg, this.elements_to_animate);
}
}
@ -133,7 +133,7 @@ export default class PieChart extends BaseChart {
if(flag){
transform(path,this.calTranslateByAngle(this.slicesProperties[i]));
path.style.fill = lightenDarkenColor(color,50);
let g_off = get_offset(this.svg);
let g_off = getOffset(this.svg);
let x = e.pageX - g_off.left + 10;
let y = e.pageY - g_off.top - 10;
let title = (this.formatted_labels && this.formatted_labels.length>0
@ -165,7 +165,7 @@ export default class PieChart extends BaseChart {
mouseLeave(){
this.hoverSlice(this.curActiveSlice,this.curActiveSliceIndex,false);
}
bind_tooltip() {
bindTooltip() {
this.drawArea.addEventListener('mousemove',this.mouseMove);
this.drawArea.addEventListener('mouseleave',this.mouseLeave);
}
@ -179,7 +179,7 @@ export default class PieChart extends BaseChart {
if(d) {
let stats = $.create('div', {
className: 'stats',
inside: this.stats_wrapper
inside: this.statsWrapper
});
stats.innerHTML = `<span class="indicator">
<i style="background-color:${color};"></i>

49
src/js/config.js Normal file
View File

@ -0,0 +1,49 @@
import Chart from './chart';
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: []
};
// 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 function getDifferentChart(type, current_type, args) {
if(type === current_type) return;
if(!ALL_CHART_TYPES.includes(type)) {
console.error(`'${type}' is not a valid chart type.`);
}
if(!COMPATIBLE_CHARTS[current_type].includes(type)) {
console.error(`'${current_type}' chart cannot be converted to a '${type}' chart.`);
}
// whether the new chart can use the existing colors
const useColor = COLOR_COMPATIBLE_CHARTS[current_type].includes(type);
// Okay, this is anticlimactic
// this function will need to actually be 'changeChartType(type)'
// that will update only the required elements, but for now ...
return new Chart({
parent: args.parent,
title: args.title,
data: args.data,
type: type,
height: args.height,
colors: useColor ? args.colors : undefined
});
}

View File

@ -43,7 +43,7 @@ $.create = (tag, o) => {
return element;
};
export function get_offset(element) {
export function getOffset(element) {
let rect = element.getBoundingClientRect();
return {
// https://stackoverflow.com/a/7436602/6495043