[component] axes components working

This commit is contained in:
pratu16x7 2017-12-05 01:31:41 +05:30
parent 02685dab7e
commit cbb1242fe7
15 changed files with 634 additions and 1390 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -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
},

View File

@ -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
}

View File

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

View File

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

View File

@ -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);
}

View File

@ -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 = [];

View File

@ -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 {
//
}

View File

@ -1,4 +1,4 @@
import { fillArray } from '../utils/helpers';
import { fillArray } from './helpers';
const MIN_BAR_PERCENT_HEIGHT = 0.01;

View File

@ -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;

View File

@ -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