[component] axes components working
This commit is contained in:
parent
02685dab7e
commit
cbb1242fe7
1034
dist/frappe-charts.esm.js
vendored
1034
dist/frappe-charts.esm.js
vendored
File diff suppressed because it is too large
Load Diff
2
dist/frappe-charts.min.cjs.js
vendored
2
dist/frappe-charts.min.cjs.js
vendored
File diff suppressed because one or more lines are too long
2
dist/frappe-charts.min.esm.js
vendored
2
dist/frappe-charts.min.esm.js
vendored
File diff suppressed because one or more lines are too long
2
dist/frappe-charts.min.iife.js
vendored
2
dist/frappe-charts.min.iife.js
vendored
File diff suppressed because one or more lines are too long
2
docs/assets/js/frappe-charts.min.js
vendored
2
docs/assets/js/frappe-charts.min.js
vendored
File diff suppressed because one or more lines are too long
@ -8,7 +8,7 @@ let bar_composite_data = {
|
||||
"2013", "2014", "2015", "2016", "2017"],
|
||||
|
||||
datasets: [{
|
||||
"title": "Events",
|
||||
"label": "Events",
|
||||
"values": report_count_list,
|
||||
// "formatted": report_count_list.map(d => d + " reports")
|
||||
}]
|
||||
@ -77,15 +77,15 @@ let type_data = {
|
||||
|
||||
datasets: [
|
||||
{
|
||||
title: "Some Data",
|
||||
label: "Some Data",
|
||||
values: [25, 40, 30, 35, 8, 52, 17, -4]
|
||||
},
|
||||
{
|
||||
title: "Another Set",
|
||||
label: "Another Set",
|
||||
values: [25, 50, -10, 15, 18, 32, 27, 14]
|
||||
},
|
||||
{
|
||||
title: "Yet Another",
|
||||
label: "Yet Another",
|
||||
values: [15, 20, -3, -15, 58, 12, -17, 37]
|
||||
}
|
||||
]
|
||||
@ -211,8 +211,8 @@ let update_data = {
|
||||
}],
|
||||
"specific_values": [
|
||||
{
|
||||
title: "Altitude",
|
||||
// title: "A very long text",
|
||||
label: "Altitude",
|
||||
// label: "A very long text",
|
||||
line_type: "dashed",
|
||||
value: 38
|
||||
},
|
||||
|
||||
@ -64,15 +64,15 @@
|
||||
|
||||
datasets: [
|
||||
{
|
||||
title: "Some Data",
|
||||
label: "Some Data",
|
||||
values: [25, 40, 30, 35, 8, 52, 17, -4]
|
||||
},
|
||||
{
|
||||
title: "Another Set",
|
||||
label: "Another Set",
|
||||
values: [25, 50, -10, 15, 18, 32, 27, 14]
|
||||
},
|
||||
{
|
||||
title: "Yet Another",
|
||||
label: "Yet Another",
|
||||
values: [15, 20, -3, -15, 58, 12, -17, 37]
|
||||
}
|
||||
]
|
||||
@ -144,7 +144,7 @@
|
||||
|
||||
data.specific_values = [
|
||||
{
|
||||
title: "Altitude",
|
||||
label: "Altitude",
|
||||
line_type: "dashed", // or "solid"
|
||||
value: 38
|
||||
}
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
import BaseChart from './BaseChart';
|
||||
import { ChartComponent, IndexedChartComponent } from '../objects/ChartComponent';
|
||||
import { getOffset, fire } from '../utils/dom';
|
||||
import { AxisChartRenderer } from '../utils/draw';
|
||||
import { equilizeNoOfElements } from '../utils/draw-utils';
|
||||
import { Animator } from '../utils/animate';
|
||||
import { runSMILAnimation } from '../utils/animation';
|
||||
import { calcIntervals } from '../utils/intervals';
|
||||
import { floatTwo } from '../utils/helpers';
|
||||
import { calcIntervals, getIntervalSize, getValueRange, getZeroIndex } from '../utils/intervals';
|
||||
import { floatTwo, fillArray } from '../utils/helpers';
|
||||
|
||||
export default class AxisChart extends BaseChart {
|
||||
constructor(args) {
|
||||
@ -16,560 +17,14 @@ export default class AxisChart extends BaseChart {
|
||||
this.zeroLine = this.height;
|
||||
}
|
||||
|
||||
parseData() {
|
||||
let args = this.rawChartArgs;
|
||||
this.xAxisLabels = args.data.labels || [];
|
||||
this.y = args.data.datasets || [];
|
||||
|
||||
this.y.forEach(function(d, i) {
|
||||
d.index = i;
|
||||
}, this);
|
||||
checkData(data) {
|
||||
return true;
|
||||
}
|
||||
|
||||
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() {
|
||||
getFirstUpdateData(data) {
|
||||
//
|
||||
}
|
||||
|
||||
// this should be inherent in BaseChart
|
||||
refreshRenderer() {
|
||||
// These args are basically the current state of the chart,
|
||||
// with constant and alive params mixed
|
||||
this.renderer = new AxisChartRenderer({
|
||||
totalHeight: this.height,
|
||||
totalWidth: this.width,
|
||||
zeroLine: this.zeroLine,
|
||||
avgUnitWidth: this.avgUnitWidth,
|
||||
xAxisMode: this.xAxisMode,
|
||||
yAxisMode: this.yAxisMode
|
||||
});
|
||||
}
|
||||
|
||||
setupComponents() {
|
||||
// Must have access to all current data things
|
||||
let self = this;
|
||||
this.yAxis = {
|
||||
layerClass: 'y axis',
|
||||
layer: undefined,
|
||||
make: self.makeYLines.bind(self),
|
||||
makeArgs: [self.yAxisPositions, self.yAxisLabels],
|
||||
store: [],
|
||||
// animate? or update? will come to while implementing
|
||||
animate: self.animateYLines,
|
||||
// indexed: 1 // ?? As per datasets?
|
||||
};
|
||||
this.xAxis = {
|
||||
layerClass: 'x axis',
|
||||
layer: undefined,
|
||||
make: self.makeXLines.bind(self),
|
||||
// TODO: will implement series skip with avgUnitWidth and isSeries later
|
||||
makeArgs: [self.xPositions, self.xAxisLabels],
|
||||
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,
|
||||
// make: makeYMarkerLines,
|
||||
// makeArgs: [this.yMarkerPositions, this.yMarker],
|
||||
// store: [],
|
||||
// animate: animateYMarkerLines
|
||||
};
|
||||
this.xMarkerLines = {
|
||||
// layerClass: 'x marker axis',
|
||||
// layer: undefined,
|
||||
// make: makeXMarkerLines,
|
||||
// makeArgs: [this.xMarkerPositions, this.xMarker],
|
||||
// store: [],
|
||||
// animate: animateXMarkerLines
|
||||
};
|
||||
|
||||
// Marker Regions
|
||||
|
||||
this.components = [
|
||||
this.yAxis,
|
||||
this.xAxis,
|
||||
// this.yMarkerLines,
|
||||
// this.xMarkerLines,
|
||||
// this.dataUnits,
|
||||
];
|
||||
}
|
||||
|
||||
setup_values() {
|
||||
this.data.datasets.map(d => {
|
||||
d.values = d.values.map(val => (!isNaN(val) ? val : 0));
|
||||
});
|
||||
this.setup_x();
|
||||
this.setup_y();
|
||||
}
|
||||
|
||||
setup_x() {
|
||||
this.set_avgUnitWidth_and_x_offset();
|
||||
if(this.xPositions) {
|
||||
this.x_old_axis_positions = this.xPositions.slice();
|
||||
}
|
||||
this.xPositions = this.xAxisLabels.map((d, i) =>
|
||||
floatTwo(this.x_offset + i * this.avgUnitWidth));
|
||||
|
||||
if(!this.x_old_axis_positions) {
|
||||
this.x_old_axis_positions = this.xPositions.slice();
|
||||
}
|
||||
}
|
||||
|
||||
setup_y() {
|
||||
if(this.yAxisLabels) {
|
||||
this.y_old_axis_values = this.yAxisLabels.slice();
|
||||
}
|
||||
|
||||
let values = this.get_all_y_values();
|
||||
|
||||
if(this.y_sums && this.y_sums.length > 0) {
|
||||
values = values.concat(this.y_sums);
|
||||
}
|
||||
|
||||
this.yAxisLabels = calcIntervals(values, this.type === 'line');
|
||||
|
||||
if(!this.y_old_axis_values) {
|
||||
this.y_old_axis_values = this.yAxisLabels.slice();
|
||||
}
|
||||
|
||||
const y_pts = this.yAxisLabels;
|
||||
const value_range = y_pts[y_pts.length-1] - y_pts[0];
|
||||
|
||||
if(this.multiplier) this.old_multiplier = this.multiplier;
|
||||
this.multiplier = this.height / value_range;
|
||||
if(!this.old_multiplier) this.old_multiplier = this.multiplier;
|
||||
|
||||
const interval = y_pts[1] - y_pts[0];
|
||||
const interval_height = interval * this.multiplier;
|
||||
|
||||
let zero_index;
|
||||
|
||||
if(y_pts.indexOf(0) >= 0) {
|
||||
// the range has a given zero
|
||||
// zero-line on the chart
|
||||
zero_index = y_pts.indexOf(0);
|
||||
} else if(y_pts[0] > 0) {
|
||||
// Minimum value is positive
|
||||
// zero-line is off the chart: below
|
||||
let min = y_pts[0];
|
||||
zero_index = (-1) * min / interval;
|
||||
} else {
|
||||
// Maximum value is negative
|
||||
// zero-line is off the chart: above
|
||||
let max = y_pts[y_pts.length - 1];
|
||||
zero_index = (-1) * max / interval + (y_pts.length - 1);
|
||||
}
|
||||
|
||||
if(this.zeroLine) this.old_zeroLine = this.zeroLine;
|
||||
this.zeroLine = this.height - (zero_index * interval_height);
|
||||
if(!this.old_zeroLine) this.old_zeroLine = this.zeroLine;
|
||||
|
||||
// Make positions arrays for y elements
|
||||
if(this.yAxisPositions) this.oldYAxisPositions = this.yAxisPositions;
|
||||
this.yAxisPositions = this.yAxisLabels.map(d => this.zeroLine - d * this.multiplier);
|
||||
if(!this.oldYAxisPositions) this.oldYAxisPositions = this.yAxisPositions;
|
||||
|
||||
// if(this.yAnnotationPositions) this.oldYAnnotationPositions = this.yAnnotationPositions;
|
||||
// this.yAnnotationPositions = this.specific_values.map(d => this.zeroLine - d.value * this.multiplier);
|
||||
// if(!this.oldYAnnotationPositions) this.oldYAnnotationPositions = this.yAnnotationPositions;
|
||||
}
|
||||
|
||||
makeXLines(positions, values) {
|
||||
// TODO: draw as per condition (with/without label etc.)
|
||||
return positions.map((position, i) => this.renderer.xLine(position, values[i]));
|
||||
}
|
||||
|
||||
makeYLines(positions, values) {
|
||||
return positions.map((position, i) => this.renderer.yLine(position, values[i]));
|
||||
}
|
||||
|
||||
draw_graph(init=false) {
|
||||
// TODO: NO INIT!
|
||||
if(this.raw_chart_args.hasOwnProperty("init") && !this.raw_chart_args.init) {
|
||||
this.y.map((d, i) => {
|
||||
d.svg_units = [];
|
||||
this.make_path && this.make_path(d, this.xPositions, d.yUnitPositions, this.colors[i]);
|
||||
this.makeUnits(d);
|
||||
this.calcYDependencies();
|
||||
});
|
||||
return;
|
||||
}
|
||||
if(init) {
|
||||
this.draw_new_graph_and_animate();
|
||||
return;
|
||||
}
|
||||
this.y.map((d, i) => {
|
||||
d.svg_units = [];
|
||||
this.make_path && this.make_path(d, this.xPositions, d.yUnitPositions, this.colors[i]);
|
||||
this.makeUnits(d);
|
||||
});
|
||||
}
|
||||
|
||||
draw_new_graph_and_animate() {
|
||||
let data = [];
|
||||
this.y.map((d, i) => {
|
||||
// Anim: Don't draw initial values, store them and update later
|
||||
d.yUnitPositions = new Array(d.values.length).fill(this.zeroLine); // no value
|
||||
data.push({values: d.values});
|
||||
d.svg_units = [];
|
||||
|
||||
this.make_path && this.make_path(d, this.xPositions, d.yUnitPositions, this.colors[i]);
|
||||
this.makeUnits(d);
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
this.updateData(data);
|
||||
}, 350);
|
||||
}
|
||||
|
||||
setupNavigation(init) {
|
||||
if(init) {
|
||||
// Hack: defer nav till initial updateData
|
||||
setTimeout(() => {
|
||||
super.setupNavigation(init);
|
||||
}, 500);
|
||||
} else {
|
||||
super.setupNavigation(init);
|
||||
}
|
||||
}
|
||||
|
||||
makeUnits(d) {
|
||||
this.makeDatasetUnits(
|
||||
this.xPositions,
|
||||
d.yUnitPositions,
|
||||
this.colors[d.index],
|
||||
d.index,
|
||||
this.y.length
|
||||
);
|
||||
}
|
||||
|
||||
makeDatasetUnits(x_values, y_values, color, dataset_index,
|
||||
no_of_datasets, units_group, units_array, unit) {
|
||||
|
||||
if(!units_group) units_group = this.svg_units_groups[dataset_index];
|
||||
if(!units_array) units_array = this.y[dataset_index].svg_units;
|
||||
if(!unit) unit = this.unit_args;
|
||||
|
||||
units_group.textContent = '';
|
||||
units_array.length = 0;
|
||||
|
||||
let unit_AxisChartRenderer = new AxisChartRenderer(this.height, this.zeroLine, this.avgUnitWidth);
|
||||
|
||||
y_values.map((y, i) => {
|
||||
let data_unit = unit_AxisChartRenderer[unit.type](
|
||||
x_values[i],
|
||||
y,
|
||||
unit.args,
|
||||
color,
|
||||
i,
|
||||
dataset_index,
|
||||
no_of_datasets
|
||||
);
|
||||
units_group.appendChild(data_unit);
|
||||
units_array.push(data_unit);
|
||||
});
|
||||
|
||||
if(this.isNavigable) {
|
||||
this.bind_units(units_array);
|
||||
}
|
||||
}
|
||||
|
||||
bindTooltip() {
|
||||
// TODO: could be in tooltip itself, as it is a given functionality for its parent
|
||||
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.translateY * 2) {
|
||||
this.mapTooltipXPosition(relX);
|
||||
} else {
|
||||
this.tip.hide_tip();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
mapTooltipXPosition(relX) {
|
||||
if(!this.y_min_tops) return;
|
||||
|
||||
let titles = this.xAxisLabels;
|
||||
if(this.format_tooltip_x && this.format_tooltip_x(this.xAxisLabels[0])) {
|
||||
titles = this.xAxisLabels.map(d=>this.format_tooltip_x(d));
|
||||
}
|
||||
|
||||
let y_format = this.format_tooltip_y && this.format_tooltip_y(this.y[0].values[0]);
|
||||
|
||||
for(var i=this.xPositions.length - 1; i >= 0 ; i--) {
|
||||
let x_val = this.xPositions[i];
|
||||
// let delta = i === 0 ? this.avgUnitWidth : x_val - this.xPositions[i-1];
|
||||
if(relX > x_val - this.avgUnitWidth/2) {
|
||||
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) => {
|
||||
return {
|
||||
title: set.title,
|
||||
value: y_format ? this.format_tooltip_y(set.values[i]) : set.values[i],
|
||||
color: this.colors[j],
|
||||
};
|
||||
});
|
||||
|
||||
this.tip.set_values(x, y, title, '', values);
|
||||
this.tip.show_tip();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// API
|
||||
updateData(newY, newX) {
|
||||
if(!newX) {
|
||||
newX = this.xAxisLabels;
|
||||
}
|
||||
this.updating = true;
|
||||
|
||||
this.old_x_values = this.xAxisLabels.slice();
|
||||
this.old_y_axis_tops = this.y.map(d => d.yUnitPositions.slice());
|
||||
|
||||
this.old_y_values = this.y.map(d => d.values);
|
||||
|
||||
// Just update values prop, setup_x/y() will do the rest
|
||||
if(newY) this.y.map(d => {d.values = newY[d.index].values;});
|
||||
if(newX) this.xAxisLabels = newX;
|
||||
|
||||
this.setup_x();
|
||||
this.setup_y();
|
||||
|
||||
// Change in data, so calculate dependencies
|
||||
this.calcYDependencies();
|
||||
|
||||
// Got the values? Now begin drawing
|
||||
this.animator = new Animator(this.height, this.width, this.zeroLine, this.avgUnitWidth);
|
||||
|
||||
this.animate_graphs();
|
||||
|
||||
this.updating = false;
|
||||
}
|
||||
|
||||
animate_graphs() {
|
||||
this.elements_to_animate = [];
|
||||
// Pre-prep, equilize no of positions between old and new
|
||||
let [old_x, newX] = equilizeNoOfElements(
|
||||
this.x_old_axis_positions.slice(),
|
||||
this.xPositions.slice()
|
||||
);
|
||||
|
||||
let [oldYAxis, newYAxis] = equilizeNoOfElements(
|
||||
this.oldYAxisPositions.slice(),
|
||||
this.yAxisPositions.slice()
|
||||
);
|
||||
|
||||
let newXValues = this.xAxisLabels.slice();
|
||||
let newYValues = this.yAxisLabels.slice();
|
||||
|
||||
let extra_points = this.xPositions.slice().length - this.x_old_axis_positions.slice().length;
|
||||
|
||||
if(extra_points > 0) {
|
||||
this.makeXLines(old_x, newXValues);
|
||||
}
|
||||
// No Y extra check?
|
||||
this.makeYLines(oldYAxis, newYValues);
|
||||
|
||||
// Animation
|
||||
if(extra_points !== 0) {
|
||||
this.animateXLines(old_x, newX);
|
||||
}
|
||||
this.animateYLines(oldYAxis, newYAxis);
|
||||
|
||||
this.y.map(d => {
|
||||
let [old_y, newY] = equilizeNoOfElements(
|
||||
this.old_y_axis_tops[d.index].slice(),
|
||||
d.yUnitPositions.slice()
|
||||
);
|
||||
if(extra_points > 0) {
|
||||
this.make_path && this.make_path(d, old_x, old_y, this.colors[d.index]);
|
||||
this.makeDatasetUnits(old_x, old_y, this.colors[d.index], d.index, this.y.length);
|
||||
}
|
||||
// Animation
|
||||
d.path && this.animate_path(d, newX, newY);
|
||||
this.animate_units(d, newX, newY);
|
||||
});
|
||||
|
||||
runSMILAnimation(this.chartWrapper, this.svg, this.elements_to_animate);
|
||||
|
||||
setTimeout(() => {
|
||||
this.y.map(d => {
|
||||
this.make_path && this.make_path(d, this.xPositions, d.yUnitPositions, this.colors[d.index]);
|
||||
this.makeUnits(d);
|
||||
|
||||
this.makeYLines(this.yAxisPositions, this.yAxisLabels);
|
||||
this.makeXLines(this.xPositions, this.xAxisLabels);
|
||||
// this.make_y_specifics(this.yAnnotationPositions, this.specific_values);
|
||||
});
|
||||
}, 400);
|
||||
}
|
||||
|
||||
animate_path(d, newX, newY) {
|
||||
const newPointsList = newY.map((y, i) => (newX[i] + ',' + y));
|
||||
this.elements_to_animate = this.elements_to_animate
|
||||
.concat(this.animator.path(d, newPointsList.join("L")));
|
||||
}
|
||||
|
||||
animate_units(d, newX, newY) {
|
||||
let type = this.unit_args.type;
|
||||
|
||||
d.svg_units.map((unit, i) => {
|
||||
if(newX[i] === undefined || newY[i] === undefined) return;
|
||||
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
|
||||
newX[i],
|
||||
newY[i],
|
||||
d.index,
|
||||
this.y.length
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
animateXLines(oldX, newX) {
|
||||
this.xAxisLines.map((xLine, i) => {
|
||||
this.elements_to_animate.push(this.animator.verticalLine(
|
||||
xLine, newX[i], oldX[i]
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
animateYLines(oldY, newY) {
|
||||
this.yAxisLines.map((yLine, i) => {
|
||||
this.elements_to_animate.push(this.animator.horizontalLine(
|
||||
yLine, newY[i], oldY[i]
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
animateYAnnotations() {
|
||||
//
|
||||
}
|
||||
|
||||
add_data_point(y_point, x_point, index=this.xAxisLabels.length) {
|
||||
let newY = this.y.map(data_set => { return {values:data_set.values}; });
|
||||
newY.map((d, i) => { d.values.splice(index, 0, y_point[i]); });
|
||||
let newX = this.xAxisLabels.slice();
|
||||
newX.splice(index, 0, x_point);
|
||||
|
||||
this.updateData(newY, newX);
|
||||
}
|
||||
|
||||
remove_data_point(index = this.xAxisLabels.length-1) {
|
||||
if(this.xAxisLabels.length < 3) return;
|
||||
|
||||
let newY = this.y.map(data_set => { return {values:data_set.values}; });
|
||||
newY.map((d) => { d.values.splice(index, 1); });
|
||||
let newX = this.xAxisLabels.slice();
|
||||
newX.splice(index, 1);
|
||||
|
||||
this.updateData(newY, newX);
|
||||
}
|
||||
|
||||
getDataPoint(index=this.currentIndex) {
|
||||
// check for length
|
||||
let data_point = {
|
||||
index: index
|
||||
};
|
||||
let y = this.y[0];
|
||||
['svg_units', 'yUnitPositions', 'values'].map(key => {
|
||||
let data_key = key.slice(0, key.length-1);
|
||||
data_point[data_key] = y[key][index];
|
||||
});
|
||||
data_point.label = this.xAxisLabels[index];
|
||||
return data_point;
|
||||
}
|
||||
|
||||
updateCurrentDataPoint(index) {
|
||||
index = parseInt(index);
|
||||
if(index < 0) index = 0;
|
||||
if(index >= this.xAxisLabels.length) index = this.xAxisLabels.length - 1;
|
||||
if(index === this.currentIndex) return;
|
||||
this.currentIndex = index;
|
||||
fire(this.parent, "data-select", this.getDataPoint());
|
||||
}
|
||||
|
||||
set_avgUnitWidth_and_x_offset() {
|
||||
// Set the ... you get it
|
||||
this.avgUnitWidth = this.width/(this.xAxisLabels.length - 1);
|
||||
this.x_offset = 0;
|
||||
}
|
||||
|
||||
get_all_y_values() {
|
||||
let all_values = [];
|
||||
|
||||
// Add in all the y values in the datasets
|
||||
this.y.map(d => {
|
||||
all_values = all_values.concat(d.values);
|
||||
});
|
||||
|
||||
// Add in all the specific values
|
||||
return all_values.concat(this.specific_values.map(d => d.value));
|
||||
}
|
||||
|
||||
calcYDependencies() {
|
||||
this.y_min_tops = new Array(this.xAxisLabels.length).fill(9999);
|
||||
this.y.map(d => {
|
||||
@ -583,4 +38,156 @@ export default class AxisChart extends BaseChart {
|
||||
// this.chartWrapper.removeChild(this.tip.container);
|
||||
// this.make_tooltip();
|
||||
}
|
||||
|
||||
prepareData() {
|
||||
let s = this.state;
|
||||
s.xAxisLabels = this.data.labels || [];
|
||||
s.datasetLength = s.xAxisLabels.length;
|
||||
|
||||
let zeroArray = new Array(s.datasetLength).fill(0);
|
||||
|
||||
s.datasets = this.data.datasets;
|
||||
if(!this.data.datasets) {
|
||||
// default
|
||||
s.datasets = [{
|
||||
values: zeroArray
|
||||
}];
|
||||
}
|
||||
|
||||
s.datasets.map((d, i)=> {
|
||||
let vals = d.values;
|
||||
if(!vals) {
|
||||
vals = zeroArray;
|
||||
} else {
|
||||
// Check for non values
|
||||
vals = vals.map(val => (!isNaN(val) ? val : 0));
|
||||
|
||||
// Trim or extend
|
||||
if(vals.length > s.datasetLength) {
|
||||
vals = vals.slice(0, s.datasetLength);
|
||||
} else {
|
||||
vals = fillArray(vals, s.datasetLength - vals.length, 0);
|
||||
}
|
||||
}
|
||||
|
||||
d.index = i;
|
||||
});
|
||||
}
|
||||
|
||||
reCalc() {
|
||||
let s = this.state;
|
||||
|
||||
// X
|
||||
s.xAxisLabels = this.data.labels;
|
||||
this.calcXPositions();
|
||||
|
||||
// Y
|
||||
s.datasetsLabels = this.data.datasets.map(d => d.label);
|
||||
|
||||
// s.datasetsValues = [[]]; indexed component
|
||||
// s.datasetsValues = [[[12, 34, 68], [10, 5, 46]], [[20, 20, 20]]]; // array of indexed components
|
||||
s.datasetsValues = s.datasets.map(d => d.values); // indexed component
|
||||
s.yAxisLabels = calcIntervals(this.getAllYValues(), this.type === 'line');
|
||||
this.calcYAxisPositions();
|
||||
|
||||
// *** this.state.datasetsPoints =
|
||||
}
|
||||
|
||||
calcXPositions() {
|
||||
let s = this.state;
|
||||
this.setUnitWidthAndXOffset();
|
||||
s.xPositions = s.xAxisLabels.map((d, i) =>
|
||||
floatTwo(s.xOffset + i * s.unitWidth));
|
||||
}
|
||||
|
||||
calcYAxisPositions() {
|
||||
let s = this.state;
|
||||
const yPts = s.yAxisLabels;
|
||||
|
||||
s.scaleMultiplier = this.height / getValueRange(yPts);
|
||||
const intervalHeight = getIntervalSize(yPts) * s.scaleMultiplier;
|
||||
s.zeroLine = this.height - (getZeroIndex(yPts) * intervalHeight);
|
||||
|
||||
s.yAxisPositions = yPts.map(d => s.zeroLine - d * s.scaleMultiplier);
|
||||
}
|
||||
|
||||
setUnitWidthAndXOffset() {
|
||||
this.state.unitWidth = this.width/(this.state.datasetLength - 1);
|
||||
this.state.xOffset = 0;
|
||||
}
|
||||
|
||||
getAllYValues() {
|
||||
// TODO: yMarkers, regions, sums, every Y value ever
|
||||
return [].concat(...this.state.datasetsValues);
|
||||
}
|
||||
|
||||
calcIntermedState() {
|
||||
//
|
||||
}
|
||||
|
||||
// this should be inherent in BaseChart
|
||||
refreshRenderer() {
|
||||
// These args are basically the current state of the chart,
|
||||
// with constant and alive params mixed
|
||||
let state = {
|
||||
totalHeight: this.height,
|
||||
totalWidth: this.width,
|
||||
|
||||
xAxisMode: this.config.xAxisMode,
|
||||
yAxisMode: this.config.yAxisMode,
|
||||
|
||||
zeroLine: this.state.zeroLine,
|
||||
unitWidth: this.state.unitWidth,
|
||||
};
|
||||
if(!this.renderer) {
|
||||
this.renderer = new AxisChartRenderer(state);
|
||||
} else {
|
||||
this.renderer.refreshState(state);
|
||||
}
|
||||
}
|
||||
|
||||
setupComponents() {
|
||||
this.yAxis = new ChartComponent({
|
||||
layerClass: 'y axis',
|
||||
make: (renderer, positions, values) => {
|
||||
return positions.map((position, i) => renderer.yLine(position, values[i]));
|
||||
},
|
||||
argsKeys: ['yAxisPositions', 'yAxisLabels'],
|
||||
animate: () => {}
|
||||
});
|
||||
|
||||
this.xAxis = new ChartComponent({
|
||||
layerClass: 'x axis',
|
||||
make: (renderer, positions, values) => {
|
||||
return positions.map((position, i) => renderer.xLine(position, values[i]));
|
||||
},
|
||||
argsKeys: ['xPositions', 'xAxisLabels'],
|
||||
animate: () => {}
|
||||
});
|
||||
|
||||
// Indexed according to dataset
|
||||
|
||||
// this.dataUnits = new IndexedChartComponent({
|
||||
// layerClass: 'x axis',
|
||||
// make: (renderer, positions, values) => {
|
||||
// return positions.map((position, i) => renderer.xLine(position, values[i]));
|
||||
// },
|
||||
// argsKeys: ['xPositions', 'xAxisLabels'],
|
||||
// animate: () => {}
|
||||
// });
|
||||
|
||||
this.yMarkerLines = {};
|
||||
this.xMarkerLines = {};
|
||||
|
||||
// Marker Regions
|
||||
|
||||
this.components = [
|
||||
this.yAxis,
|
||||
this.xAxis,
|
||||
// this.yMarkerLines,
|
||||
// this.xMarkerLines,
|
||||
// this.dataUnits,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -3,13 +3,21 @@ import AxisChart from './AxisChart';
|
||||
export default class BarChart extends AxisChart {
|
||||
constructor(args) {
|
||||
super(args);
|
||||
|
||||
this.type = 'bar';
|
||||
this.xAxisMode = args.xAxisMode || 'tick';
|
||||
this.yAxisMode = args.yAxisMode || 'span';
|
||||
this.setup();
|
||||
}
|
||||
|
||||
configure(args) {
|
||||
super.configure(args);
|
||||
this.config.xAxisMode = args.xAxisMode || 'tick';
|
||||
this.config.yAxisMode = args.yAxisMode || 'span';
|
||||
}
|
||||
|
||||
setUnitWidthAndXOffset() {
|
||||
this.state.unitWidth = this.width/(this.state.datasetLength + 1);
|
||||
this.state.xOffset = this.state.unitWidth;
|
||||
}
|
||||
|
||||
setup_values() {
|
||||
super.setup_values();
|
||||
this.x_offset = this.avgUnitWidth;
|
||||
@ -74,8 +82,5 @@ export default class BarChart extends AxisChart {
|
||||
this.updateCurrentDataPoint(this.currentIndex + 1);
|
||||
}
|
||||
|
||||
set_avgUnitWidth_and_x_offset() {
|
||||
this.avgUnitWidth = this.width/(this.xAxisLabels.length + 1);
|
||||
this.x_offset = this.avgUnitWidth;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -14,6 +14,7 @@ export default class BaseChart {
|
||||
colors = [],
|
||||
|
||||
isNavigable = 0,
|
||||
showLegend = 1,
|
||||
|
||||
type = '',
|
||||
|
||||
@ -31,23 +32,26 @@ export default class BaseChart {
|
||||
this.currentIndex = 0;
|
||||
}
|
||||
|
||||
this.setupConfiguration();
|
||||
this.configure(arguments[0]);
|
||||
}
|
||||
|
||||
setupConfiguration() {
|
||||
configure(args) {
|
||||
// Make a this.config, that has stuff like showTooltip,
|
||||
// showLegend, which then all functions will check
|
||||
|
||||
this.setColors();
|
||||
this.setMargins();
|
||||
this.setMargins(args);
|
||||
|
||||
// constants
|
||||
this.config = {
|
||||
showTooltip: 1,
|
||||
showTooltip: 1, // calculate
|
||||
showLegend: 1,
|
||||
isNavigable: 0,
|
||||
animate: 1
|
||||
// animate: 1
|
||||
animate: 0
|
||||
};
|
||||
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
setColors() {
|
||||
@ -67,8 +71,8 @@ export default class BaseChart {
|
||||
this.colors = this.colors.map(color => getColor(color));
|
||||
}
|
||||
|
||||
setMargins() {
|
||||
let height = this.rawChartArgs.height;
|
||||
setMargins(args) {
|
||||
let height = args.height;
|
||||
this.baseHeight = height;
|
||||
this.height = height - 40;
|
||||
this.translateX = 60;
|
||||
@ -103,7 +107,7 @@ export default class BaseChart {
|
||||
}
|
||||
|
||||
checkData() {}
|
||||
getFirstUpdateData(data) {}
|
||||
getFirstUpdateData() {}
|
||||
|
||||
setup() {
|
||||
if(this.validate()) {
|
||||
@ -114,9 +118,7 @@ export default class BaseChart {
|
||||
_setup() {
|
||||
this.bindWindowEvents();
|
||||
this.setupConstants();
|
||||
|
||||
// this.setupComponents();
|
||||
|
||||
this.setupComponents();
|
||||
this.makeContainer();
|
||||
this.makeTooltip(); // without binding
|
||||
this.draw(true);
|
||||
@ -158,32 +160,35 @@ export default class BaseChart {
|
||||
this.bindTooltip();
|
||||
}
|
||||
|
||||
bindTooltip() {}
|
||||
|
||||
draw(init=false) {
|
||||
// difference from update(): draw the whole object due to groudbreaking event (init, resize, etc.)
|
||||
// (draw everything, layers, groups, units)
|
||||
this.calc();
|
||||
this.refreshRenderer() // this chart's rendered with the config
|
||||
this.setupComponents();
|
||||
|
||||
this.calcWidth();
|
||||
this.refresh(); // refresh conponent with chart a
|
||||
|
||||
this.makeChartArea();
|
||||
this.makeLayers();
|
||||
this.setComponentParent();
|
||||
this.makeComponentLayers();
|
||||
|
||||
this.renderComponents(); // with zero values
|
||||
this.renderLegend();
|
||||
this.setupNavigation(init);
|
||||
|
||||
this.renderComponents(); // first time plain render, so no rerender
|
||||
|
||||
if(this.config.animate) this.update(this.firstUpdateData);
|
||||
}
|
||||
|
||||
update() {
|
||||
// difference from draw(): yes you do rerender everything here as well,
|
||||
// but not things like the chart itself, mosty only at component level
|
||||
this.reCalc();
|
||||
// but not things like the chart itself or layers, mosty only at component level
|
||||
// HERE IS WHERE THE ACTUAL STATE CHANGES, and old one matters, not in draw
|
||||
this.refresh();
|
||||
this.reRender();
|
||||
}
|
||||
|
||||
refreshRenderer() {}
|
||||
|
||||
calcWidth() {
|
||||
let outerAnnotationsWidth = 0;
|
||||
// let charWidth = 8;
|
||||
@ -197,9 +202,12 @@ export default class BaseChart {
|
||||
this.width = this.baseWidth - this.translateX * 2;
|
||||
}
|
||||
|
||||
calc() {
|
||||
this.calcWidth();
|
||||
refresh() { //?? refresh?
|
||||
this.oldState = this.state ? Object.assign({}, this.state) : {};
|
||||
this.prepareData();
|
||||
this.reCalc();
|
||||
this.refreshRenderer();
|
||||
this.refreshComponents();
|
||||
}
|
||||
|
||||
makeChartArea() {
|
||||
@ -218,33 +226,21 @@ export default class BaseChart {
|
||||
);
|
||||
}
|
||||
|
||||
prepareData() {}
|
||||
|
||||
makeLayers() {
|
||||
this.components.forEach((component) => {
|
||||
component.layer = this.makeLayer(component.layerClass);
|
||||
});
|
||||
}
|
||||
reCalc() {}
|
||||
// Will update values(state)
|
||||
// Will recalc specific parts depending on the update
|
||||
|
||||
calculateValues() {}
|
||||
|
||||
renderComponents() {
|
||||
this.components.forEach(c => {
|
||||
c.store = c.make(...c.makeArgs);
|
||||
c.layer.textContent = '';
|
||||
c.store.forEach(element => {c.layer.appendChild(element);});
|
||||
});
|
||||
}
|
||||
|
||||
reCalc() {
|
||||
// Will update values(state)
|
||||
// Will recalc specific parts depending on the update
|
||||
}
|
||||
refreshRenderer() {}
|
||||
|
||||
reRender(animate=true) {
|
||||
if(!animate) {
|
||||
this.renderComponents();
|
||||
return;
|
||||
}
|
||||
this.intermedState = this.calcIntermedState();
|
||||
this.refreshComponents();
|
||||
this.animateComponents();
|
||||
setTimeout(() => {
|
||||
this.renderComponents();
|
||||
@ -253,19 +249,23 @@ export default class BaseChart {
|
||||
// (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);});
|
||||
});
|
||||
calcIntermedState() {}
|
||||
|
||||
// convenient component array abstractions
|
||||
setComponentParent() { this.components.forEach(c => c.setupParent(this.drawArea)); };
|
||||
makeComponentLayers() { this.components.forEach(c => c.makeLayer()); }
|
||||
renderComponents() { this.components.forEach(c => c.render()); }
|
||||
animateComponents() { this.components.forEach(c => c.animate()); }
|
||||
refreshComponents() {
|
||||
let args = {
|
||||
chartState: this.state,
|
||||
oldChartState: this.oldState,
|
||||
intermedState: this.intermedState,
|
||||
chartRenderer: this.renderer
|
||||
};
|
||||
this.components.forEach(c => c.refresh(args));
|
||||
}
|
||||
|
||||
|
||||
calcInitStage() {}
|
||||
|
||||
|
||||
renderLegend() {}
|
||||
|
||||
setupNavigation(init=false) {
|
||||
@ -309,10 +309,6 @@ export default class BaseChart {
|
||||
getDataPoint() {}
|
||||
updateCurrentDataPoint() {}
|
||||
|
||||
makeLayer(className, transform='') {
|
||||
return makeSVGGroup(this.drawArea, className, transform);
|
||||
}
|
||||
|
||||
getDifferentChart(type) {
|
||||
return getDifferentChart(type, this.type, this.rawChartArgs);
|
||||
}
|
||||
|
||||
@ -4,27 +4,27 @@ import { makeSVGGroup, makePath, makeGradient } from '../utils/draw';
|
||||
export default class LineChart extends AxisChart {
|
||||
constructor(args) {
|
||||
super(args);
|
||||
|
||||
this.xAxisMode = args.xAxisMode || 'span';
|
||||
this.yAxisMode = args.yAxisMode || 'span';
|
||||
|
||||
if(args.hasOwnProperty('show_dots')) {
|
||||
this.show_dots = args.show_dots;
|
||||
} else {
|
||||
this.show_dots = 1;
|
||||
}
|
||||
this.region_fill = args.region_fill;
|
||||
this.type = 'line';
|
||||
|
||||
if(Object.getPrototypeOf(this) !== LineChart.prototype) {
|
||||
return;
|
||||
}
|
||||
this.dot_radius = args.dot_radius || 4;
|
||||
this.heatline = args.heatline;
|
||||
this.type = 'line';
|
||||
|
||||
this.setup();
|
||||
}
|
||||
|
||||
configure(args) {
|
||||
super.configure(args);
|
||||
this.config.xAxisMode = args.xAxisMode || 'span';
|
||||
this.config.yAxisMode = args.yAxisMode || 'span';
|
||||
|
||||
this.config.dot_radius = args.dot_radius || 4;
|
||||
|
||||
this.config.heatline = args.heatline || 0;
|
||||
this.config.region_fill = args.region_fill || 0;
|
||||
this.config.show_dots = args.show_dots || 1;
|
||||
}
|
||||
|
||||
setupPreUnitLayers() {
|
||||
// Path groups
|
||||
this.paths_groups = [];
|
||||
|
||||
@ -1,26 +1,51 @@
|
||||
// export default class ChartComponent {
|
||||
// constructor({
|
||||
// parent = null,
|
||||
// colors = []
|
||||
// }) {
|
||||
// this.parent = parent;
|
||||
// this.colors = colors;
|
||||
// this.title_name = '';
|
||||
// this.title_value = '';
|
||||
// this.list_values = [];
|
||||
// this.title_value_first = 0;
|
||||
import { makeSVGGroup } from '../utils/draw';
|
||||
|
||||
// this.x = 0;
|
||||
// this.y = 0;
|
||||
export class ChartComponent {
|
||||
constructor({
|
||||
layerClass = '',
|
||||
layerTransform = '',
|
||||
make,
|
||||
argsKeys,
|
||||
animate
|
||||
}) {
|
||||
this.layerClass = layerClass; // 'y axis'
|
||||
this.layerTransform = layerTransform;
|
||||
this.make = make;
|
||||
this.argsKeys = argsKeys;//['yAxisPositions', 'yAxisLabels'];
|
||||
this.animate = animate;
|
||||
|
||||
// this.top = 0;
|
||||
// this.left = 0;
|
||||
this.layer = undefined;
|
||||
this.store = []; //[[]] depends on indexed
|
||||
}
|
||||
|
||||
// this.setup();
|
||||
// }
|
||||
refresh(args) {
|
||||
this.chartState = args.chartState;
|
||||
this.oldChartState = args.oldChartState;
|
||||
this.intermedState = args.intermedState;
|
||||
|
||||
// setup() {
|
||||
// this.make_tooltip();
|
||||
// }
|
||||
this.chartRenderer = args.chartRenderer;
|
||||
}
|
||||
|
||||
// }
|
||||
render() {
|
||||
let args = this.argsKeys.map(key => this.chartState[key]);
|
||||
args.unshift(this.chartRenderer);
|
||||
this.store = this.make(...args);
|
||||
|
||||
this.layer.textContent = '';
|
||||
this.store.forEach(element => {
|
||||
this.layer.appendChild(element);
|
||||
});
|
||||
}
|
||||
|
||||
setupParent(parent) {
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
makeLayer() {
|
||||
this.layer = makeSVGGroup(this.parent, this.layerClass, this.layerTransform);
|
||||
}
|
||||
}
|
||||
|
||||
export class IndexedChartComponent extends ChartComponent {
|
||||
//
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
import { fillArray } from '../utils/helpers';
|
||||
import { fillArray } from './helpers';
|
||||
|
||||
const MIN_BAR_PERCENT_HEIGHT = 0.01;
|
||||
|
||||
|
||||
@ -205,10 +205,10 @@ export function makeHoriYLine(y, label, totalWidth, mode) {
|
||||
|
||||
export class AxisChartRenderer {
|
||||
constructor(state) {
|
||||
this.updateState(state);
|
||||
this.refreshState(state);
|
||||
}
|
||||
|
||||
updateState(state) {
|
||||
refreshState(state) {
|
||||
this.totalHeight = state.totalHeight;
|
||||
this.totalWidth = state.totalWidth;
|
||||
this.zeroLine = state.zeroLine;
|
||||
|
||||
@ -153,6 +153,35 @@ export function calcIntervals(values, withMinimum=false) {
|
||||
return intervals;
|
||||
}
|
||||
|
||||
export function getZeroIndex(yPts) {
|
||||
let zeroIndex;
|
||||
let interval = getIntervalSize(yPts);
|
||||
if(yPts.indexOf(0) >= 0) {
|
||||
// the range has a given zero
|
||||
// zero-line on the chart
|
||||
zeroIndex = yPts.indexOf(0);
|
||||
} else if(yPts[0] > 0) {
|
||||
// Minimum value is positive
|
||||
// zero-line is off the chart: below
|
||||
let min = yPts[0];
|
||||
zeroIndex = (-1) * min / interval;
|
||||
} else {
|
||||
// Maximum value is negative
|
||||
// zero-line is off the chart: above
|
||||
let max = yPts[yPts.length - 1];
|
||||
zeroIndex = (-1) * max / interval + (yPts.length - 1);
|
||||
}
|
||||
return zeroIndex;
|
||||
}
|
||||
|
||||
export function getIntervalSize(orderedArray) {
|
||||
return orderedArray[1] - orderedArray[0];
|
||||
}
|
||||
|
||||
export function getValueRange(orderedArray) {
|
||||
return orderedArray[orderedArray.length-1] - orderedArray[0];
|
||||
}
|
||||
|
||||
export function calcDistribution(values, distributionSize) {
|
||||
// Assume non-negative values,
|
||||
// implying distribution minimum at zero
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user